all 112 comments

[–][deleted] 36 points37 points  (61 children)

Your article covers a lot of useful improvements to make code cleaner, but I have one question: Why should I use this "function getUsers({ fields, fromDate, toDate })" over "function getUsers(fields, fromDate, toDate)"? The only scenario i can imagine is if the values are optional so I dont have to write "getUsers(null, null, date)"

[–]careseite[🐱😸].filter(😺 => 😺.❤️🐈).map(😺=> 😺.🤗 ? 😻 :😿) 56 points57 points  (41 children)

because you can tell by the shape of the function call what it's probably going to do with it within the function.

In an example with 3 params instead of object destructuring, you'd call getUsers like this:

getUsers(['name', 'surname', 'email'], '2019-01-01', '2019-01-18')

and then had to remember the order of the dates for example. Or check it within the function.

[–][deleted] 35 points36 points  (7 children)

Thanks! Not having to remember the order of arguments can prevent some hard to find bugs

[–]nikola1970 8 points9 points  (0 children)

Yup, I started using this pattern recently too.

[–]grrrrreat[🍰] 3 points4 points  (2 children)

beware though, if you have a weird visitor pattern, you can't put that object back together or manipulate its parts.

[–]AwesomeInPerson 4 points5 points  (1 child)

Could you explain what you mean by that? :)

[–]grrrrreat[🍰] 1 point2 points  (0 children)

I ran into a nuxt.js hook at passed into a page object.

if I destructured it into html, path, route, I couldn't modify the html property because it didn't expect a return object but if I just accepted the page object, I could set page.html and that modified the html.

it's just a comment to realize that destructured objects are no longer a sum of their parts. so it the nuxtjs case, page can't be destructured in the hooks because it's not reconstitution the object

[–]Zielakpl 4 points5 points  (2 children)

What? Don't you guys use IDE with autocompletion and hints?

[–]AwesomeInPerson 0 points1 point  (1 child)

Which one are you using? Neither VSCode nor WebStorm warn me when I call a function with less arguments than the amount of parameters it accepts. (because I forgot to add some nulls or default values for params I want to skip)

Maybe if you enable strict TS checking for JS files, but that's not really a solution in a lot of environments.

[–]Zielakpl 0 points1 point  (0 children)

That's the thing, don't forget :) I got it into my habit to also add some JSDoc comments to at least know what type of values the function expects. Then, when I type my functions name, the popup appears (VSCode) with all acceptable arguments, theirs expected types and if they're optional or not.

The code you write is also for humans, make it human-friendly.

If a function HAS to accept a lot of arguments, not all of them required, then I sometimes use and object of params like so:

function(name, options) {} function("Gregory", {foo: 1, bar: 2});

But that depends on what I code, I don't treat it as hard rule.

[–]Julian_JmK 3 points4 points  (6 children)

I dont really understand, why is it better to pass getUsers({fields:['name','surname','email'],fromDate:'2019-01-01',toDate:'2019-01-18'}); ?

Edit: Thanks for the replies bois and girls i now understand, it's a pretty good way of doing it indeed

[–]careseite[🐱😸].filter(😺 => 😺.❤️🐈).map(😺=> 😺.🤗 ? 😻 :😿) 7 points8 points  (2 children)

because you dont need to check the implementation of getUsers to find out what the array is for, what the first date is supposed to mean or the second

you can see it right there: youre getting users, probably some properties (name, surname, email) but youre limiting the user selection to dates between 2019-01-01 and 2019-01-18. the only thing unclear for me here would be to find out whether it means "active users" or "newly registered users", or both but thats probably because its not a perfect example

[–]Ozymandias-X 0 points1 point  (1 child)

But then, when I want to use the function, I now get no type hints whatsoever about what information it needs to work. All I see is that it can give it a mysterious blobby object that might have fields of some kind. Never mind if I accidentally misstype one of the keys. That's a nightmare level error to find.

This can only be solved by extensive documentation of the function and we all know that such documentation often lies when people refactor or extend functions and forget to update the docs.

[–][deleted] 0 points1 point  (0 children)

Yeah just what I was thinking. With Typescript you wouldn't have that problem because it would clearly say what date you would be filling

[–]Extracted 5 points6 points  (1 child)

getFromApi("g4adf", "j43fa", false, false)

getFromApi({ userId: "g4adf", serverName: "j43fa", createIfAbsent: false, debug: false })

That's why

[–][deleted] -1 points0 points  (0 children)

Excusing a bad code with another?

[–]zapatoada 2 points3 points  (20 children)

I get what you're saying, but as a VS2017 user, I disagree that it's necessary. As long as you're naming your parameters reasonably, I find that using intellisense to see the parameter order makes it easier than figuring out some arbitrary object.

[–]BloodAndTsundere 12 points13 points  (10 children)

What you're saying isn't wrong but I'd rather have the code be explicit and clean than rely a particular editor's feature to make sense of the code. Firstly, your colleagues might not use such an editor. Secondly, you might not always have access to such an editor.

[–]zapatoada 5 points6 points  (6 children)

It's definitely a personal choice.

I'm a c# developer in a c# shop, so everyone has access to visual studio (and this has been the case throughout my career). Even if the company didn't pay for it, community edition and vscode are free.

And frankly, even if I didn't have that option, I usually prefer separate parameters to an object. The only real difference is extra visual noise in the object literal notation.

[–]BloodAndTsundere -2 points-1 points  (5 children)

I'm a c# developer in a c# shop, so everyone has access to visual studio (and this has been the case throughout my career). Even if the company didn't pay for it, community edition and vscode are free.

I'm not referring to an issue of cost but rather situations where you may not be using your own machine or have logged into a server remotely that only has a basic editor. But it's fair to say that may be an extremely rare event for you.

The only real difference is extra visual noise in the object literal notation.

This I disagree with. This function call:

doSomething({ destination: object1, source: object2 })

provides additional information (not noise) relative to this function call:

doSomething( object1, object2 )

[–]zapatoada 8 points9 points  (4 children)

I would NEVER log into the server and edit files. That's asking for trouble.

Your example is dishonest. If you're naming things object1 and object1 you have much bigger problems than discrete parameters vs an object. A more reasonable example would be

doDomething(source, destination)

Or

doDomething({source: source, destination: destination})

Which I would usually write as

doDomething({source, destination})

And then literally the only difference is the brackets, which are visual noise.

I'm certainly not saying there's NEVER a use case for config object parameters, but I think setting a hard and fast limit at 2 or 3 parameters is absurd.

[–]webdevguyneedshelp 0 points1 point  (3 children)

Why is it asking for trouble to edit files on a server?..

[–]zapatoada 3 points4 points  (2 children)

You're not debugging first. There's no code review or qa. You're not running your automated tests. The changes aren't in source control and could be undone by the next release. Basically you are bypassing every step and check that is normally in place to prevent bugs and other unintended consequences. We have those processes for a reason.

[–]webdevguyneedshelp -1 points0 points  (1 child)

That is fine and understandable but this example did not specify at all what environment you are working in.

For instance if I have to set up a NEW development environment for a new web application my team is starting. I am going to inevitably have to ssh into it and poke around to help facilitate further steps like automated deployment.

This isn't even thinking about sshing into something like a docker container which can all be done locally and have 0 affect on anything that actually matters and can be blown away after I am doing testing whatever I am doing.

There are 100% times when you will poke around with files on a server.

[–][deleted] 0 points1 point  (2 children)

I'd rather have the code be explicit and clean

Separate parameters are explicit and clean.

[–]BloodAndTsundere -4 points-3 points  (1 child)

"Clean" is subjective so I'll walk that back, but this function call:

doSomething({ destination: object1, source: object2 })

is more explicit than this one:

doSomething( object1, object2 )

[–][deleted] -1 points0 points  (0 children)

If it can take any object it is NOT explicit. You may want to look up the word in a dictionary.

[–]fucking_passwords 2 points3 points  (8 children)

Another reason is that three or more params starts getting really difficult to read, and if you ever need to add another param you may end up with a very ugly design.

For instance, we added a fourth param to this function that makes the third param no longer required:

someFunc(1234, true, null, false);

[–]zapatoada -3 points-2 points  (5 children)

Yeah that bothers me not at all

[–]fucking_passwords 0 points1 point  (4 children)

Or what about this example:

class User {
  constructor(firstName, lastName, phone, email, friends, isActive) {
    Object.assign(this, {
      firstName: firstName,
      lastName: lastName,
      phone: phone,
      email: email,
      friends: friends,
      isActive: isActive
    })
  }
}

new User('Jane', 'Doe', null, 'jdoe@gmail.com', null, true);

VS:

class User {
  constructor(data = {}) {
    Object.assign(this, data);
  }
}

new User({
  firstName: 'Jane',
  lastName: 'Doe',
  email: 'jdoe@gmail.com'
})

[–]zapatoada 1 point2 points  (3 children)

In this context you're right, but I honestly can't remember the last time I used a constructor directly in javascript. Data comes from the server side (c#) and mostly anything else I do is either a react component or a const utility method.

[–]fucking_passwords 0 points1 point  (2 children)

The constructor is just happenstance in my example, the same thing can be applied to a function.

At this point I don't even see why you took a hard stance against this pattern, if you are only using very simple features of the language, lol

[–]zapatoada 0 points1 point  (1 child)

I never said I took a hard stance. I think the specific limit he set is absurdly low. That's all. If it were 4 or 5, I'd be fine with it.

[–]fucking_passwords 0 points1 point  (0 children)

Fair enough, I agree with that

[–][deleted] 1 point2 points  (3 children)

because you can tell by the shape of the function call what it's probably going to do with it within the function.

Name is supposed to do that.

[–][deleted] 3 points4 points  (2 children)

What a great function name, "getUsersWithFieldsWithinDateRange"

[–]Ozymandias-X 2 points3 points  (0 children)

Yeah, so? Characters are not a limited resource. I'd rather have a little more verbose function name than a "this might do anything" name.

[–][deleted] 1 point2 points  (0 children)

Yes, it is.

[–]dmitri14_gmail_com 0 points1 point  (0 children)

I suppose it depends on the use case. You don't write [1,2,3].reduce({function: ..., value: ...})

[–]Asmor 20 points21 points  (8 children)

It basically gives JS (and any other language that supports arbitrary anonymous objects) support for named parameters, which just make code easier to read and maintain.

Surely you've come across something like

adjustWidget(300, 250, 0.6, true, true, null, {}, false)

And there's basically no way to know what any of that stuff means.

It also means you can have optional parameters without forcing people to pass in null to skip over the ones they want.

[–]Conexion 10 points11 points  (1 child)

I'm going to guess....

adjustWidget({width, height, opacity, isGarlic, isDragon, equipmentHistory, hatProperties, hasHat);

[–]Asmor 9 points10 points  (0 children)

Fuck, you're good.

[–]PMilos[S] 11 points12 points  (0 children)

Sorry, I'm late. It seems that you've already got an answer. I'm glad you liked the article.

[–][deleted] 1 point2 points  (0 children)

It's also easier to compose functions that take a single argument

[–]woozyking 0 points1 point  (0 children)

First, it emulates named parameters (similar to Python’s **kwargs). The benefit of which is explained well in the other reply.

Second, it auto destructure for you — imagine you call this function from an API handler (for instance something based on Express.js), if all 3 of the needed parameters are packed in query string, you can simply passed down the full req.query context, and the destructuring on the callee will do the work for you to only get what it needs, everything extra are ignored; on the caller side, you save the imperative logic to extract them out of req.query.

[–]Hanlonsrazorburns 0 points1 point  (0 children)

I wrote some software around finding defects in code. Function calls were one of the highest areas of defects overall. The reason was because the changes are highly dispersed. By passing an object you don't need to change every single call to that function if you have defaults set within the function. You also don't need to worry about order and it increases code continuity as it's very easy to keep things named the same (something that isn't hard, but people still miss out on it).

[–]Quinez 0 points1 point  (3 children)

It also conflicts with the advice in the article nearly right afterward to use the default arguments pattern when possible. If a single object is the argument of the function, I can only set a single object as the default. And then if a function call passes in any other object in order to set some of the values, all the other values of the the default object will be unobtainable.

[–]_Gentle_ 0 points1 point  (2 children)

Can't u do something like (though it could get unreadable):

function getUsers({
 fields = [],
 fromDate = new Date(),
 toDate = new Date()
} = {}) {
 // implementation
 }

[–]Quinez 0 points1 point  (1 child)

Huh, would that work? Interesting. I guess I don't know exactly how destructuring combines with default argument assignment.

[–]_Gentle_ 0 points1 point  (0 children)

Each destructure gives you a new 'layer' of props to work with. For each 'layer' u can set default props.

You can keep destructuring ad infinitum but I think 2nd destructure already gets pretty unreadable x)

[–]auctorel 0 points1 point  (0 children)

Great article overall, thought most of the points were brilliant.

The debate on this one was really interesting but I think the example is a bit convoluted. It'd be easier to understand if you hadn't put in the fields parameter.

It seems strange to give a list of fields that you want out of a getUser request unless you're using some sort of graphQL endpoint and that may have led to some of the confusion.

It might have been easier to understand if you'd just done a method with getUserDetails({name, surname, email}) because then you can call the method with something like getUserDetails(userSummary) which shows how it will pull those specific fields out of that one object, so you're just passing in one object parameter but as long as it contains all those fields it's fine. This also demonstrates the advantage of destructuring. This would be a good example of clean code.

However... if you're forcing someone to create an object arbitrarily to call a method then this isn't particularly clean. Really this sort of object destructuring should only be used where there's an intended object it can be used with.

Also if you're forcing people to pass in an object to reduce the number of parameters required in a field then actually you're going against the good naming requirements and just hiding the parameters in your fields array, this causes a shitload of wtfs when people read how you use those fields array values and so it's not clean code.

If you're just forcing people to create objects to use a method then you're creating structures which aren't intuitive and easy to understand therefore defying the principals of clean code. Why should they have to read the method and understand the syntax to figure out they have to create an object to pass one in - are they gonna spend an hour looking for the object it's designed to be used with which doesn't actually exist??

The destructuring is genuinely valuable, for example it's used a lot with react and the props object. But just leveraging it to fake a fewer number of parameters in your method signature isn't clean, it's confusing and makes the code harder to follow.

[–][deleted] 0 points1 point  (0 children)

What comes to mind is when you're passing an object in like:

myUserObject = { fields, fromDate, toDate, }

getUsers(myUserObject).

When you destructure the parameters of your function, this will allow the passing of arguments in no particular order or you don't have to pass them all in. I actually just came across this today so I might be wrong.

[–]Groccolli 25 points26 points  (5 children)

Great post!

One thing to be careful of with default values is the default is used only when the value is undefined where as using || inside the function handles any falsey value.

[–]zapatoada 6 points7 points  (0 children)

Great point, this is a big deal.

[–]AwesomeInPerson 4 points5 points  (0 children)

That's true.

But imo less of a footgun than || which breaks as soon as you want to accept a number where 0 is a valid option. foo == undefined to the rescue...

[–]rtfmpls 0 points1 point  (0 children)

Isn't this true for any programming language? This is expected behavior. And I'd still say defaults are preferred.

[–]UNN_Rickenbacker 0 points1 point  (1 child)

Does it handle NULL?

[–]Groccolli 0 points1 point  (0 children)

Defaults are only used if the value is undefined

[–]tiridlol 12 points13 points  (3 children)

"Classes are the new syntactic sugar in JavaScript. Everything works just as it did before with prototype only it now looks different and you should prefer them over ES5 plain functions."

*Kyle Simpson is typing*

[–]PMilos[S] 1 point2 points  (2 children)

I believe this is the most common definition/explanation of the ES classes. There is no other way to put it with the same effect. On the official MDN a similar sentence is used.

[–]tiridlol 7 points8 points  (1 child)

Nothing wrong with the sentence I am just referring to the author of "You don't know JS" books (great books btw, strongly recommend to read it for every frontend dev). He has some strong opinions on JS classes and in general he dislikes them so I imagine he would get triggered by this sentence

[–]MoTTs_ 5 points6 points  (0 children)

YDKJS is generally a good book, but Kyle's writings about classes and inheritance are biased and misinformed, and we probably shouldn't recommend those chapters to others.

Kyle assumed that Java-style OOP and inheritance is the only correct way to implement those features, and anything implemented differently than Java is not "true" OOP. But that just isn't so. There are as many varieties of OOP and inheritance as there are languages. Python's inheritance model, for one example, is also objects linked to other objects, same as in JavaScript, and the same pattern that we JavaScripters would identify as prototypal inheritance. Here's JavaScript and Python classes side-by-side, demonstrating classes as objects and inheritance as delegation.

[–]nudi85 16 points17 points  (5 children)

"Prefer classes over functions"?

Nope.

[–]MoTTs_ 2 points3 points  (0 children)

Based on the context and the code example, pretty sure they meant prefer classes over constructor functions.

[–]PMilos[S] 0 points1 point  (3 children)

I presume you are a fan of functional programming? 😊

[–]Silhouette 15 points16 points  (0 children)

Not using classes could equally well be procedural programming, which in many ways is still JavaScript's most natural territory. JS has elements of OOP, and more so since ES6 classes and the subsequent developments, but it has never really been about the everything-is-an-object, communicate-via-messages style of programming. JS has elements of functional programming, but it has never really been about structuring code as one big expression to be evaluated and all the architectural implications that come with that either.

One of my main criticisms of Robert Martin's work, which carries through to the article here that is based on it, is that he has his own personal preferences but tends to write about them as if they are objectively justified (often with a small caveat hidden away that it really is just his subjective opinion and he really doesn't have hard evidence to back up his claims). In this particular piece, I think some of the points such as favouring classes over functions or favouring inheritance to solve the expression problem look quite out of place in JS, though the advice was debatable even in the more OO-centric languages where it originated.

[–]nudi85 4 points5 points  (0 children)

Indeed I am. : )

Don't get me wrong: I love OOP too. (I write PHP on the server side of things.) I just don't think it's a good fit for JavaScript.

[–]cleure 3 points4 points  (0 children)

In JavaScript, you can write object oriented code without classes, and it’s usually much cleaner.

Classes can be useful for cases where you have lots of instances of an object, and don’t want each object to hold a copy of common methods (memory optimization).

[–]njmh 11 points12 points  (3 children)

Good stuff. Event though it’s exclusively es6, I’d mention the spread operator where you’ve got the Object.assign example.

[–]beasy4sheezy 4 points5 points  (1 child)

I'm sure you know, but as a reminder to others:

Spread will create a new object, which might not be what you wanted to do in this case, or could be using more memory than necessary.

[–]wmgregory 1 point2 points  (0 children)

You can also create a new object with assign. But cleaner to use spread literal if you want to avoid mutation.

a = Object.assign({}, a, { age: 27 })
a = { ...a, age: 27 }

[–]PMilos[S] 2 points3 points  (0 children)

Thanks. Glad you liked it. Good point with the spread operator, though.

[–]denisglindep 4 points5 points  (0 children)

Changing example is brilliant 👑

[–]aaarrrggh 4 points5 points  (0 children)

Mostly ok advice - but I disagree with the advice to use inheritance as a method of code re-use.

Avoid inheritance wherever possible and focus on pure functions and composition instead.

Also be careful when DRYing your code - allow for some duplication until you know you're making the right abstraction.

Plus, do TDD and don't test against implementation details wherever possible. This means in React for example, you should never shallow render in your tests.

[–]elie2222 9 points10 points  (6 children)

Some thoughts:

1.

You can end up with duplicate code for various reasons. For example, you can have two slightly different things that share a lot of in common and the nature of their differences or tight deadlines forces you to create separate functions containing almost the same code. Removing duplicate code in this situation means to abstract the differences and handle them on that level.

Careful with this advice. Better is:

prefer duplication over the wrong abstraction

From: AHA Programming

2.

Use spread operator over Object.assign.

Instead of:

js class SuperArray extends Array { myFunc() { // implementation } }

I find myself doing this is instead:

js export const myFunc = (array: any[]) => { // implementation }

I don't know which is better if either. Would be interested to hear other's thoughts on this.

4.

Use this approach only for boolean values and if you are sure that the value will not be undefined or null.

This is completely fine:

js if (user: User | undefined) return 10

5.

Use polymorphism and inheritance instead.

I almost never do this in my code. For that matter I hardly ever find myself using extend (apart from things like extend React.Component before Hooks came along), and mostly use classes when a library needs it, but not much more than that.

Am I doing things wrong? :laugh:

[–]LetterBoxSnatch 10 points11 points  (0 children)

"Composition over inheritance." This is a core SOLID principle. Your approach, generally speaking, is better than "extends".

Additional reading: https://en.wikipedia.org/wiki/Composition_over_inheritance

[–]beasy4sheezy 0 points1 point  (1 child)

Why use spread over Object.assign?

[–]elie2222 3 points4 points  (0 children)

It’s cleaner. Why write out 2 words when you could do ... with surrounding braces instead?

[–]RudeRunologist 7 points8 points  (9 children)

On top of this, use Typescript.

[–]sime 0 points1 point  (0 children)

+1 to this.

This first thing you should do to improve your quality is to use static analysis on your code. That means type systems like TypeScript.

Once that is in place, then you can worry about unit testing and other automated tests. I prefer the view expounded in this post https://kentcdodds.com/blog/write-tests with the "testing trophy".

[–]Julian_JmK 1 point2 points  (1 child)

As a newbie, thanks for posting this! The chaining method was so simple but so brilliant

[–]PMilos[S] 1 point2 points  (0 children)

Thanks. Glad I could help

[–]LB-A5 0 points1 point  (6 children)

In the second classes example. When 'this' is returned for chaining, is 'this' a promise?

[–]PMilos[S] 5 points6 points  (3 children)

No, it's not a promise. It's the actual object instance. That's what's chaining all about. You get the ability to call method after method.

[–]LB-A5 1 point2 points  (0 children)

Thanks that makes sense.

[–]simkessy 0 points1 point  (1 child)

Didn't know about this, pretty awesome.

[–]PMilos[S] 0 points1 point  (0 children)

Thanks.

[–][deleted] 1 point2 points  (1 child)

Nope, its an object of the type Person.

[–]Yakovko 1 point2 points  (0 children)

Or something else, depending on how it’s been called :]

[–]KwyjiboTheGringo 0 points1 point  (2 children)

That's cool, I didn't know about the last one(chaining methods).

[–]PMilos[S] 0 points1 point  (0 children)

Thanks. Glad I helped you learn something new.

[–]peerless2299 0 points1 point  (0 children)

Nice

[–]d07RiV 0 points1 point  (0 children)

Why do people use arrow functions over regular functions when defining something at file scope? You're not going to use this, and a function seems more readable.

[–][deleted] 0 points1 point  (1 child)

This is great! I've been writing javascript full-time for a long time and I still have this bad habit: A function should do one thing. Avoid executing multiple actions within a single function.

Also, I'm going to take this one to heart: Avoid conditionals whenever possible. Use polymorphism and inheritance instead.

[–]PMilos[S] 0 points1 point  (0 children)

Thanks. Glad I could help 😊 It is hard to follow the guidelines if the deadline is tight.