When you have multiple parameters that are the same type, though, that isn’t foolproof. Maybe don’t do it everywhere, but do do it when misuse is likely (function foo(a: string, b: string, c: string): [string, string, string])
Give the params proper names, and the IDE will reveal them (inline even!) without RORO or its costs. Sure, for a return value like that, use an object with proper names. But that’s been a best practice without “RORO”.
Sorry, my example was bad - my point is that if you have it literally be named parameters in the RORO case there’s much lower chance to screw it up since the labels are apparent without needing to hover or trigger any kind of dialog. Chance you’ll mix them up just goes down, so while in theory it’s basically the same as the editor help case, in practice it’s way more foolproof.
Would be cool though if VSCode and other editors had a way to make variable names somehow persistently visible on function invocations - that would be pretty interesting IMO!
> Would be cool though if VSCode and other editors had a way to make variable names somehow persistently visible on function invocations - that would be pretty interesting IMO!
Looks like you missed what I wrote (twice). They do. Others have pointed out that VSCode supports it too. Search the comments for "inlay hints".
You also are shown the parameter names and types when writing the function call.
What does the parameter mean? Is it in the doc block? (Almost never.) Does it default to false? (Now you’re reading the whole potential call stack.) Are additional behaviors associated with its absence or trueness that aren’t inferred at the call site? (Who knows but it’s a fair roll of the dice!)
It’s really common in JS. Typically the default is false, but an unreasonably large minority including some really notable cases default true… which makes it that much worse.
You write a function that does one thing, and then you realise it can be used for something else that's almost the same thing, so you add an optional boolean parameter.
What's infuriating about this? If you give those variables good, descriptive names, and provide a default argument for the optional variable (which is just basic good programming practice), then what's the problem?
The problem is none of those things are visible where they matter. Sure your function signature might say what “true” means, and what parameter “true” is assigned to. Your IDE will probably even give you hints as you type.
Those won’t show up in code review. They won’t show up in diffs at all if you rename the parameter and change its semantics.
I see this pattern used sometimes, and I agree it often makes the code more readable, but I wonder if there is any performance impact from creating and destructuring objects just to pass arguments.
There definitely is. But the vast majority of the time it’s not the performance impact that actually matters, and regardless of the merit of the idea itself it’s much more likely that teams with this kind of discipline aren’t producing the kinds of badly performing code that does matter.
I have mixed feelings about this principle as stated, but strongly favor a similar rule for function parameters (I agree with the author that always Returning an Object is low value generally).
First, mixed feelings:
- Every parameter as an object key is both overkill and can make function signatures less intuitive. If your function follows a common convention eg process(collection, argumentToProcessCollection), that’s going to be easier to understand.
- Bare, mixed-type, ambiguous primitive values are harder to understand in an argument position than clear well-defined types with named semantics.
- Booleans, opaque enums are particular red flags here.
- These ideas things can be combined.
I prefer:
1. Use a functional style, including function parameter position (and always make impure functions call out their impurity).
2. Only accept bare primitive types in positions where their meaning is unambiguous.
3. If you want to accept ambiguous primitives or even optional values with a clear type, consider whether you’re actually writing more than one function.
4. If you’re actually writing one function, everything else goes in a clearly defined options object.
5. If your options parameter starts growing, GOTO 3.
Yes, this articulates what I was thinking very well.
Binary relations / operators are a good example of 2. Things that could be infix operators but can’t because language. It’d be a nightmare if those functions required an object as an interface.
As with everything design, there is no hard and fast rule. Keyword arguments / passing an object make wonderfully clear interfaces in a number of situations and it’s helpful to have them. But applying the pattern everywhere would be a complete disaster.
I disagree. By this logic, any fetch-like interface should be decomposed back to fetch(createRequest(new RequestHeaders({…}), …, …), …, …). Big interfaces are there for a reason, and the reason is that we, as developers, are users as well, and we deserve simplicity in our daily work, because there is no deficit in complexity. Structure is only useful if you can remember it and if you use it entirely, naturally (so that you will remember it). If you can’t or you don’t, it’s useless. Why it’s okay to have ls/find/curl/etc in a shell, but having it in the source code is bad, and we have to write pages of readdirs, Regex-compiles and readyState handlers, increasing a mistake probability?
I know what your rationale is, and I agree with it: god objects and functions are hard to debug. But nothing stops you from splitting the complexity into simple, testable chunks under the hood of a god interface, which knows how to combine them by following a simple set of options. Whether you’re writing more than one function or not then becomes an implementation detail, which doesn’t land on shoulders of a user of that interface.
> I disagree. By this logic, any fetch-like interface should be decomposed back to fetch(createRequest(new RequestHeaders({…}), …, …), …, …).
This is a weird example because fetch
1. Is constrained by HTTP semantics and has limited control over how it deals with optional values.
2. Has still somehow conflated at least two functions (idempotent reads like GET/HEAD/OPTIONS vs writes)
That’s really all of the point I was trying to make with 3. The optionality of Request.body is a signal that fetch is actually expressing multiple functions.
And I chose the word “consider” deliberately. If fetch were being written in a vacuum without consideration for HTTP or historical complexities it was meant to address, I’d probably argue for breaking it up. But I would consider those other factors as well.
> Big interfaces are there for a reason, and the reason is that we, as developers, are users as well, and we deserve simplicity in our daily work, because there is no deficit in complexity.
Big interfaces may or may not provide simplicity. Big coherent interfaces which express the whole of one thing are totally different from interfaces of any size which conflate different things.
> Why it’s okay to have ls/find/curl/etc in a shell, but having it in the source code is bad, and we have to write pages of readdirs, Regex-compiles and readyState handlers, increasing a mistake probability?
I’m not arguing for consuming small individual functions with a single responsibility as if they’re the final interface. What those small functions allow is composability, and at least a chance to make invalid state unrepresentable.
> I know what your rationale is, and I agree with it: god objects and functions are hard to debug. But nothing stops you from splitting the complexity into simple, testable chunks under the hood
But those interfaces are hard to debug for users, and
> knows how to combine them by following a simple set of options
If you know how to do that, there! You’ve defined another function! Now your user can call that higher level function and ignore your lower level units.
> Whether you’re writing more than one function or not then becomes an implementation detail, which doesn’t land on shoulders of a user of that interface.
Of course. That doesn’t necessarily mean your end user interface is one function with every argument that could possibly be used to influence the behavior of the underlying implementation. That’s just leaking your implementation!
It may or may not be leaking, that depends on details which are out of my original scope. E.g. http headers is a well-defined structure. It may appear as {[h]:v} to the user, but be represented by a more complex structure in the implementation, e.g. may contain areSent() method or be implemented by a third-party lib, which doesn’t even declare the way it stores them. All of that behind the options={headers:{[h]:v}, …} interface, which does not even modify the options object. How is that leaking?
I really think fetch may be a poor example for the purposes of this discussion. Fetch is roughly analogous to HTTP, its semantics are well known and pretty clear… and importantly, its scope is unlikely to significantly grow branchy optional junk.
Again, I would not object to fetch if I encountered it in a design document or code review. That implicitly means I would not object to headers as represented in its signature (it’s just Record<string, string> after all… a very common type in plenty of well-defined interfaces).
An example that would better illustrate when to “consider whether you’re writing more than one function” would be a generalized data access function that may use the following data sources, directly accepting whatever details needed/appropriate for the specific usage:
- HTTP
- SSH
- Postgres
- Redis
- Local file system
- In-memory POJO or equivalent
Sure, you can define such a function. But can you clearly define its usage? And sure, it might defer to smaller units as an implementation detail, but is that single function actually providing any benefit to users? Won’t they (1) find it easier to reason about the different interfaces directly and (2) be able to consume or produce a more usable general function which defers to a single, consistent interface abstraction?
I mean, yeah sure now you need to reference both that function and the implementation satisfying its signature. That’s twice as many imports, surely it’s twice as bad right? Or… maybe it’s better not to have to remember (or even try to document) all the different variations of parameters you must/may pass depending on which data source you’re accessing.
We could use webpack-dev-middleware as an example, cause it has much more options and tool diversity. Or Intl.NumberFormatter, for a lesser example. If these APIs were built in a composable way, it would be even harder to wrap your head around their configuration and just too wordy to both write and read.
Sure, you can define such a function. But can you clearly define its usage?
Yes! In your example I see many db drivers for a single db/orm-like sort of api (not exactly orm, but very similar). It’s much easier to learn one common interface, and then only learn/discover differences in connection strings and subtle functions that do or do not work for a specific access method. While db/orm is not a single function, it is usually a collection of functions that target multiple very different storages (the same convergence which you find wrong with GET vs POST etc). If I were to use these native APIs, that would be mental overload x 6 for me. Libraries like sequelize and sqlalchemy are very usable because you may use them in both serious postgre projects and your pocket sqlite tools, by changing only the connection string.
Of course we discussed functions, not APIs, but if I were to design dropbox-like thing for developers, e.g. store/access your foo config anywhere, it could be:
Not because my users are too dumb or too lazy, but because in the scope of this function all of these accessors minutiae details are irrelevant to them. They just want f(data, {conn:postgreconn, table:”FooConfigs”}) or f(data, {conn:”webdav://…”, method:”put”}), or just f(data, config.foo_options), the entire options imported from devops-aware config. All of them would be described in one github readme.md page and usable in 15 minutes instead of hours of reading on manuals.
This all only works because I reduced the scope of that function to a single operation compatible with all access methods, but that’s exactly my counterpoint for 3, 5. It’s almost like cat data > /dir/file, where <dir> is a mountpoint of hdd, cdrom, nfs, webdav, mongodb, aws kms, mailbox (file is mail folder), sshfs, postgrefs (file is table name) and so on. But for developers, not shell users.
> By immediately destructuring the parameters object
Please don't try to use fancy terms to impress. That's not what destructuring is.
This appears to have been hand selected and placed into the second-chance pool. My question is why? This is such a poorly written article in so many other regards, which wouldn't be bad if it contributed something worthwhile, but it's all to document what is not a novel or noteworthy pattern.
That does, in fact, appear to be a nearly textbook example of JavaScript object destructuring. See the Mozilla Developer reference site[0], specifically the "Unpacking fields from objects passed as a function parameter" section.
I've declined PRs in the past (and had mine declined) over boolean flag args, magic numbers etc. When you work in a team readability is an important factor.
Still, I do wince sometimes at the Everest sized heaps created in JS to do even the most trivial of tasks. 10s of thousands or hundreds of thousands of objects being generated in a smallish React app.
It's fast enough, the VM is obscenely optimized, and it's readable. But what is all that stuff really _doing_?
It reminds me I recently had a company ship me a laptop with Win on it. I turned it on and logged in and the CPU was just a madhouse with Edge and 2 tabs open. What is the CPU even _doing_??? Does the company mine crypto on company laptops for cash flow... or, what?
> It reminds me I recently had a company ship me a laptop with Win on it. I turned it on and logged in and the CPU was just a madhouse with Edge and 2 tabs open. What is the CPU even _doing_??? Does the company mine crypto on company laptops for cash flow... or, what?
Even in a VM, Windows is constantly doing things in the background, whether it's touching the disk, burning CPU cycles, or making network connections. The background services are both numerous and opaque, so who knows what it's actually doing.
I've been running Arch Linux for about a decade to avoid exactly that. I mean, I put up with some crap... GPU drivers, Alsa to Pulse to Pipewire, terrible or non existent Slack, Teams, Zoom apps, bizarre overlapping partially redundant X configuration,
Xorg to Wayland, building emacs from SRC to get it to load faster... Endless crap really.
But man, it only loads services I know about and it's sooooo fast. And best of all it shuts up when I stop working :)
The closest thing to named parameters in TypeScript is using objects with readonly properties and const assertions where appropriate. It makes it so the objects are basically just fixed key/value pairs. They're used in Deno.
It isn't just about protecting the caller of a function from having to deal with the function unexpectedly modifying the object. It's also about an async function not having to make a copy of it to prevent the app calling it from changing it before it's returned.
I’ve seen this style before in Python and noticed that it gets kind of dicey once arguments become optional. Suddenly call sites don’t show all the available options and you have to grok the function implementation to get a sense of how to use it.
I’m sure there are ways to prevent that from creeping in, but it’s something I’ve noticed.
This is a strangely timed article, because just today I got fed up with this exact approach and I'm considering abandoning it.
I've been writing React this way for about 2 years now. It's very nice...for small functions. But over time, it starts to feel extremely heavy, especially with larger functions that need to take in lots of parameters. I know, I know, I could break the function down. But in some cases you're just working with complex data that has a lot of inputs.
When you're working with more than 10 or so parameters, adding and removing them feels like a very heavy chore.
That isn't a pattern, that's a preference. It's one that seemed appealing to me at one time but that I now disagree with because it's unidiomatic in JavaScript as well as Python and Ruby. I would hate to have this foisted upon me by a manager.
Side note: REST APIs should use custom headers so they can send back JSON arrays where appropriate. GitHub does this.
You lose the usefulness of IDE argument typehints (now you just get a [Object] requirement) and still need to look somewhere to know which parameters are required and which are optional in your object. And if you mistype the key/tag, nothing tells you about it.
- limit the number of parameters for most functions to at most 2.
- Use TypeScript (optional) and editor Intellisense (inline prompts) to display parameter type when writing function calls. VS Code has this by default.
- Use objects only when unavoidable (options, forms)
There seems to be a bug in the example code - which somehow demonstrates that this verbose style of writing also has its pitfalls, even for advocates of that style apparently.
Around a decade ago, before frameworks ate the frontend, something close to this was on its way to becoming an informal standard in libraries, I suspect due to influence from jquery: Required arguments (such as an ID or DOM element) were the first positional arguments (typically only one, rarely a second), then the last argument was anything optional in an object (most often configuration).
Use the right tool for the job. You can use both and it doesn't make the code any more or less readable. Using one only certainly does limit your abilities to write clean code and in the case of this pattern encourages some nasty shit.
Parameter count denotes logical branches like an if statement.
If you're passing in more than two to three Params a parameter object quickly becomes desireable.
Forcing parameter objects for one or two parameter functions seems.. stupid to say the least.
Boolean parameters are extremely rare if you're code is clean. They're essentially a code smell that indicate someone has dropped a hack in the middle of your function and handed in a parameter to toggle the behavior on in their one case against all others.
You're much better off writing a new function that triggers the new behavior and wraps a call to the old one.
Similar story for object returns, if you need to return an object that's fine but to be honest destructuring an object straight away makes code pretty hard to read in a lot of cases (not all).
For returning two parameters I'm pretty confused about the code, it's likely breaking single responsibility and signalling sideeffects.
I think positional arguments must be right up there with null as one of the biggest programming language design mistake. It makes composing functions more of a hassle than it should be
This pattern is basically like the functional programming pattern of converting functions to unary ones that accept algebraic data types. I think the benefits go beyond just named argument readability. You can easily see and construct an argument object that conforms to what the function needs, especially if you add Typescript in to statically check it.
totally agree. if you need a one off type for a function argument, i definitely think it's much cleaner to make it a type or interface and declare it right before the function like you do in the final example rather than defining in-line.
I used this pattern more often than not before typescript but now it is harder to argue that point in typescript, although I do prefer it still. Swift is readable in that it has named parameters that can be in any order similar to a javascript object but even more compact.
TypeScript doesn’t help disambiguate a poorly named boolean parameter though; it only helps when the expected value is a specific object type, and even then may be ambiguous.
The goal should be to do whatever makes the code read nicely, instead of forcing a single pattern which won’t work well in very case.
Destructuring where two funcs in the same scope return the same named object key will make your code harder to read for example.
Function options split across multiple lines also make things harder to read and make it less able to fit logic on a single screen.
I prefer less verbose code if I can have my function calls as single lines <80 char wide with self-documenting names that read like recipe instructions as to what is going on step by step.
Prettier’s line splitting makes things less readable a lot of the time. I use prettier-ignore quite a bit.
80 comments
[ 3.1 ms ] story [ 134 ms ] threadAn good IDE like WebStorm will show the parameter variable names, even inline w/out needing to hover, providing the exact same info as RORO.
On top of that use JSDoc to provide type info and documentation that shows as a tooltip. Or go whole hog and use Typescript.
Would be cool though if VSCode and other editors had a way to make variable names somehow persistently visible on function invocations - that would be pretty interesting IMO!
Looks like you missed what I wrote (twice). They do. Others have pointed out that VSCode supports it too. Search the comments for "inlay hints".
You also are shown the parameter names and types when writing the function call.
Those won’t show up in code review. They won’t show up in diffs at all if you rename the parameter and change its semantics.
First, mixed feelings:
- Every parameter as an object key is both overkill and can make function signatures less intuitive. If your function follows a common convention eg process(collection, argumentToProcessCollection), that’s going to be easier to understand.
- Bare, mixed-type, ambiguous primitive values are harder to understand in an argument position than clear well-defined types with named semantics.
- Booleans, opaque enums are particular red flags here.
- These ideas things can be combined.
I prefer:
1. Use a functional style, including function parameter position (and always make impure functions call out their impurity).
2. Only accept bare primitive types in positions where their meaning is unambiguous.
3. If you want to accept ambiguous primitives or even optional values with a clear type, consider whether you’re actually writing more than one function.
4. If you’re actually writing one function, everything else goes in a clearly defined options object.
5. If your options parameter starts growing, GOTO 3.
Binary relations / operators are a good example of 2. Things that could be infix operators but can’t because language. It’d be a nightmare if those functions required an object as an interface.
As with everything design, there is no hard and fast rule. Keyword arguments / passing an object make wonderfully clear interfaces in a number of situations and it’s helpful to have them. But applying the pattern everywhere would be a complete disaster.
I disagree. By this logic, any fetch-like interface should be decomposed back to fetch(createRequest(new RequestHeaders({…}), …, …), …, …). Big interfaces are there for a reason, and the reason is that we, as developers, are users as well, and we deserve simplicity in our daily work, because there is no deficit in complexity. Structure is only useful if you can remember it and if you use it entirely, naturally (so that you will remember it). If you can’t or you don’t, it’s useless. Why it’s okay to have ls/find/curl/etc in a shell, but having it in the source code is bad, and we have to write pages of readdirs, Regex-compiles and readyState handlers, increasing a mistake probability?
I know what your rationale is, and I agree with it: god objects and functions are hard to debug. But nothing stops you from splitting the complexity into simple, testable chunks under the hood of a god interface, which knows how to combine them by following a simple set of options. Whether you’re writing more than one function or not then becomes an implementation detail, which doesn’t land on shoulders of a user of that interface.
This is a weird example because fetch
1. Is constrained by HTTP semantics and has limited control over how it deals with optional values.
2. Has still somehow conflated at least two functions (idempotent reads like GET/HEAD/OPTIONS vs writes)
That’s really all of the point I was trying to make with 3. The optionality of Request.body is a signal that fetch is actually expressing multiple functions.
And I chose the word “consider” deliberately. If fetch were being written in a vacuum without consideration for HTTP or historical complexities it was meant to address, I’d probably argue for breaking it up. But I would consider those other factors as well.
> Big interfaces are there for a reason, and the reason is that we, as developers, are users as well, and we deserve simplicity in our daily work, because there is no deficit in complexity.
Big interfaces may or may not provide simplicity. Big coherent interfaces which express the whole of one thing are totally different from interfaces of any size which conflate different things.
> Why it’s okay to have ls/find/curl/etc in a shell, but having it in the source code is bad, and we have to write pages of readdirs, Regex-compiles and readyState handlers, increasing a mistake probability?
I’m not arguing for consuming small individual functions with a single responsibility as if they’re the final interface. What those small functions allow is composability, and at least a chance to make invalid state unrepresentable.
> I know what your rationale is, and I agree with it: god objects and functions are hard to debug. But nothing stops you from splitting the complexity into simple, testable chunks under the hood
But those interfaces are hard to debug for users, and
> knows how to combine them by following a simple set of options
If you know how to do that, there! You’ve defined another function! Now your user can call that higher level function and ignore your lower level units.
> Whether you’re writing more than one function or not then becomes an implementation detail, which doesn’t land on shoulders of a user of that interface.
Of course. That doesn’t necessarily mean your end user interface is one function with every argument that could possibly be used to influence the behavior of the underlying implementation. That’s just leaking your implementation!
Again, I would not object to fetch if I encountered it in a design document or code review. That implicitly means I would not object to headers as represented in its signature (it’s just Record<string, string> after all… a very common type in plenty of well-defined interfaces).
An example that would better illustrate when to “consider whether you’re writing more than one function” would be a generalized data access function that may use the following data sources, directly accepting whatever details needed/appropriate for the specific usage:
- HTTP
- SSH
- Postgres
- Redis
- Local file system
- In-memory POJO or equivalent
Sure, you can define such a function. But can you clearly define its usage? And sure, it might defer to smaller units as an implementation detail, but is that single function actually providing any benefit to users? Won’t they (1) find it easier to reason about the different interfaces directly and (2) be able to consume or produce a more usable general function which defers to a single, consistent interface abstraction?
I mean, yeah sure now you need to reference both that function and the implementation satisfying its signature. That’s twice as many imports, surely it’s twice as bad right? Or… maybe it’s better not to have to remember (or even try to document) all the different variations of parameters you must/may pass depending on which data source you’re accessing.
Sure, you can define such a function. But can you clearly define its usage?
Yes! In your example I see many db drivers for a single db/orm-like sort of api (not exactly orm, but very similar). It’s much easier to learn one common interface, and then only learn/discover differences in connection strings and subtle functions that do or do not work for a specific access method. While db/orm is not a single function, it is usually a collection of functions that target multiple very different storages (the same convergence which you find wrong with GET vs POST etc). If I were to use these native APIs, that would be mental overload x 6 for me. Libraries like sequelize and sqlalchemy are very usable because you may use them in both serious postgre projects and your pocket sqlite tools, by changing only the connection string.
Of course we discussed functions, not APIs, but if I were to design dropbox-like thing for developers, e.g. store/access your foo config anywhere, it could be:
Not because my users are too dumb or too lazy, but because in the scope of this function all of these accessors minutiae details are irrelevant to them. They just want f(data, {conn:postgreconn, table:”FooConfigs”}) or f(data, {conn:”webdav://…”, method:”put”}), or just f(data, config.foo_options), the entire options imported from devops-aware config. All of them would be described in one github readme.md page and usable in 15 minutes instead of hours of reading on manuals.This all only works because I reduced the scope of that function to a single operation compatible with all access methods, but that’s exactly my counterpoint for 3, 5. It’s almost like cat data > /dir/file, where <dir> is a mountpoint of hdd, cdrom, nfs, webdav, mongodb, aws kms, mailbox (file is mail folder), sshfs, postgrefs (file is table name) and so on. But for developers, not shell users.
Please don't try to use fancy terms to impress. That's not what destructuring is.
This appears to have been hand selected and placed into the second-chance pool. My question is why? This is such a poorly written article in so many other regards, which wouldn't be bad if it contributed something worthwhile, but it's all to document what is not a novel or noteworthy pattern.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Why so negative, I’ve had this thought before and found it reassuring that I’m not the only one.
(Edited for clarity)
[0]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Still, I do wince sometimes at the Everest sized heaps created in JS to do even the most trivial of tasks. 10s of thousands or hundreds of thousands of objects being generated in a smallish React app.
It's fast enough, the VM is obscenely optimized, and it's readable. But what is all that stuff really _doing_?
It reminds me I recently had a company ship me a laptop with Win on it. I turned it on and logged in and the CPU was just a madhouse with Edge and 2 tabs open. What is the CPU even _doing_??? Does the company mine crypto on company laptops for cash flow... or, what?
Even in a VM, Windows is constantly doing things in the background, whether it's touching the disk, burning CPU cycles, or making network connections. The background services are both numerous and opaque, so who knows what it's actually doing.
But man, it only loads services I know about and it's sooooo fast. And best of all it shuts up when I stop working :)
For example, Google Closure Compiler can take
and compile it intoClojurescript made that choice, though in retrospect it kind of seems like they backed the wrong horse.
... for the developer with the latest, highest-specced machine and only an IDE their app open.
thats what i assume
https://blog.logrocket.com/const-assertions-are-the-killer-n...
It isn't just about protecting the caller of a function from having to deal with the function unexpectedly modifying the object. It's also about an async function not having to make a copy of it to prevent the app calling it from changing it before it's returned.
https://gist.github.com/lucasnad27/e93b8f316259be3299e5#clar...
I’m sure there are ways to prevent that from creeping in, but it’s something I’ve noticed.
I've been writing React this way for about 2 years now. It's very nice...for small functions. But over time, it starts to feel extremely heavy, especially with larger functions that need to take in lots of parameters. I know, I know, I could break the function down. But in some cases you're just working with complex data that has a lot of inputs.
When you're working with more than 10 or so parameters, adding and removing them feels like a very heavy chore.
Side note: REST APIs should use custom headers so they can send back JSON arrays where appropriate. GitHub does this.
You need a way to get to the next page. Some APIs might return it in the JSON response. GitHub returns it in the Link header (not really a custom header). Details here: https://docs.github.com/en/rest/guides/traversing-with-pagin...
https://code.visualstudio.com/updates/v1_60#_inlay-hints-for...
Pass descriptively named variables instead imo.
Descriptive names are not a solution when the data types are the same. It’s just as easy to mix up the parameters of the same type.
- limit the number of parameters for most functions to at most 2.
- Use TypeScript (optional) and editor Intellisense (inline prompts) to display parameter type when writing function calls. VS Code has this by default.
- Use objects only when unavoidable (options, forms)
For example:
And, if you were okay with all the defaults, the object was completely optional: And a jquery example of the pattern:Parameter count denotes logical branches like an if statement.
If you're passing in more than two to three Params a parameter object quickly becomes desireable.
Forcing parameter objects for one or two parameter functions seems.. stupid to say the least.
Boolean parameters are extremely rare if you're code is clean. They're essentially a code smell that indicate someone has dropped a hack in the middle of your function and handed in a parameter to toggle the behavior on in their one case against all others.
You're much better off writing a new function that triggers the new behavior and wraps a call to the old one.
Similar story for object returns, if you need to return an object that's fine but to be honest destructuring an object straight away makes code pretty hard to read in a lot of cases (not all).
For returning two parameters I'm pretty confused about the code, it's likely breaking single responsibility and signalling sideeffects.
It seems like destructuring came out, and suddenly people destructure everything, even if it hurts readability.
Stuff like this, I see constantly:
I write it this way: "user" here is typed, so you can hover over it/trigger autocomplete to see the props anyways.Same thing with functions, I see a lot of:
Which, beyond just being ugly (though that's an opinion I guess) takes up an ABSURD amount of lines.I do this:
> now it is harder to argue that point in typescript
IMO, TypeScript does nothing to change the necessity of the object parameter pattern.
Destructuring where two funcs in the same scope return the same named object key will make your code harder to read for example.
Function options split across multiple lines also make things harder to read and make it less able to fit logic on a single screen.
I prefer less verbose code if I can have my function calls as single lines <80 char wide with self-documenting names that read like recipe instructions as to what is going on step by step.
Prettier’s line splitting makes things less readable a lot of the time. I use prettier-ignore quite a bit.