I have a Rust project I've been working on.
It has dependencies for:
axum: the web framework in use
serde, serde_json: Serialization of API responses
sqlx: To talk to sqlite
time: Standard datetime format with localisation and serde support
tera: templating
tracing: async logging
uuid: UUID generation
Of these, I feel they're reasonable dependencies. You could maybe quibble about uuid (which itself only depends on rng + serde with the features I've enabled), or tracing maybe, but they provide clear value.
Anyway, those direct dependencies expand out to 300-odd transitive dependencies.
So what do I do? Do I write a templating engine, HTTP server implementation, SQLite driver and JSON library from scratch to build my little ebook manager?
If Software Engineers were more like "real" engineers (ie PEs) they could never sign off on a project built like this.
$ npx create-react-app my-app
$ find my-app/node_modules -name package.json | wc -l
1506The actual runtime dependencies of a react app are basically just react and react-dom.
Aside from that, I guess your other dependencies have the same problem. It's not enough for one person to be mindful if they need something fully reasonable like a web server library which then depends on a million packages to build from source. Often-used dependencies could
- work like a regular package system (e.g. Debian's) and distribute (reproducible) binaries unless explicitly asked to build from source,
- only pull optional dependencies when they're used (a web server might depend on a logrotate dependency but maybe you don't use on-disk logs at all)
- and/or be more selective in what they depend on.
None of these are quick and easy to do without downsides
* Legacy Projects that pulled them in way back when and never migrated to the native API's
* Inexperienced developers not knowing this is part of standard JS now.
You can't really do much about the former. For the latter though, NPM would be wise to plaster a link to MDN's docs on some of these packages.
That's one line of code in your utils module...
For isEven, you first need isNumber. And that’s where the complexity lies.
({}) % 2 == 0 // false
([]) % 2 == 0 // true
"" % 2 == 0 // true
"a" % 2 == 0 // false
"1" % 2 == 0 // false
"2" % 2 == 0 // true
undefined % 2 == 0 // false
null % 2 == 0 // trueI am literally not joking. https://www.npmjs.com/package/is-even
I don't know what to say.
/**
* is-odd-and-even
* Github: https://github.com/fabrisdev/is-odd-and-even
*/
import isOdd from 'is-odd'
import isEven from 'is-even'
/**
* @param {number | string} i The number to check if it's odd and even
* @returns {boolean} True if the number is odd and even, false otherwise
*/
export default function isOddAndEven(i){
return isOdd(i) && isEven(i)
}Your isEven function should check that it's operating on a number. Eg "const isEven = (i) => typeof i === 'number' && i % 2===0;" If it's not a number then calling isEven on it doesn't make sense. If the user wants to check if a string is even then they can convert it before making the call.
A proper solution would be along the lines of:
const isRealNumber = (n) => {
if (typeof n !== 'number') return false;
if (isNaN(n)) return false;
if (n === +Inf || n === -Inf) return false;
return true;
};
const isEven = (n) => {
if (!isNumber(n)) throw new Error("Numerical error: Invalid input");
return n % 2 === 0;
}
const isOdd = (n) => {
if (!isNumber(n)) throw new Error("Numerical error: Invalid input");
return n % 2 !== 0;
}
This is the additional complexity created by dynamically weakly typed languages. And that’s why we get all these BS tiny npm packages.'isEven' really shouldn't be a dependency though, either through npm or internally. 'x % 2 === 0' works fine for integers and can be inlined, or if you're using it a bunch and want slightly cleaner code, you can define it as a lambda alongside the code that uses it. Then everyone can see exactly what it's doing with the full context.
The real issue is overabstraction. Even if you do everything "right" and have it as an internal dependency with all the correct error handling, it still makes the code a pain in the ass to read.
If the goal of isEven is to handle user input, it is woefully misnamed.
Implementing your good code with no dependencies is heresy! It's a relic of the past.
</rant>.
On a serious note, you can implement a lot of things with a standard library of any language, and only time consuming machinery should be incorporated as external dependencies, but somebody didn't get the memo, I guess.