FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

[Update] "Prefer composition over inheritance" section content by tk-o · Pull Request #139 · ryanmcdermott/clean-code-javascript · GitHub

[Update] "Prefer composition over inheritance" section content - #139

Closed
tk-o wants to merge 3 commits into
ryanmcdermott:masterfrom
tk-o:master
Closed

[Update] "Prefer composition over inheritance" section content#139
tk-o wants to merge 3 commits into
ryanmcdermott:masterfrom
tk-o:master

Conversation

tk-o commented Jan 14, 2017

Copy link
Copy Markdown

This PR is regarding a discussion developers had on the issue #40.

As there was more positive reactions than negative ones I have decided to change the code example and description for the section to be more appropriate for JavaScript language and its features.

@ryanmcdermott if there is anything I can improve, fire away ;)

Comment thread README.md Outdated
(Change the caloric expenditure of all animals when they move).
you should prefer composition over inheritance where you can.

Important thing is that JavaScript uses prototypal inheritance. It means new objects are instantiated by creating delegation links using OLOO (Objects Linking to Other Objects). Therfore you should be aware that JS does not offer real classes you may now from _Java_, _C#_ or other _object-oriented languages_. You may think: _"Hold on! There are `class` and `extends` keywords so I should be able to use classical inheritance."_. These keywords are _syntactic sugar_ and they are **not** introducing a new object-oriented inheritance model to the language (#[MDN: Classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Maybe the verb is omitted after '...JS does not offer real classes you may...'

vsemozhetbyt Jan 14, 2017
edited
Loading

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

'Therfore' -> 'Therefore'

Comment thread README.md Outdated
this.email = email;
class Airplane {
constructor(initialState) {
const { name } = initialState

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Other code examples use semicolons.

Comment thread README.md Outdated

// ...
getName() {
return `${this.name}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Why not simply return this.name?

Comment thread README.md
// define base objects
const Airplane = {
getName() {
return `${this.name}`;

vsemozhetbyt Jan 14, 2017
edited
Loading

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The same here as in the line 1537: why not simply return this.name;?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I left it like this for the consistency with AirbusA380.getName method, but sure it's better to have shorter form here as the present one brings nothing and adds a few more characters.

ryanmcdermott commented Jan 14, 2017
edited
Loading

Copy link
Copy Markdown
Owner

Thanks for the great discussion in that Issue! The article from @ericelliott is a great one, and I do think we should reference it somehow about the nature of prototypal vs. classical inheritance in JavaScript.

Meta-points here on the specifics of your PR: should we be trying to summarize an article about this or would just a simple link suffice? I think Eric's article and the other ones you link to provide a really great background, but something about summarizing them confuses me slightly when I take the point of view of a novice JS developer. There's a lot of technical terms that aren't thoroughly explained like OLOO. I had to go back to the article to remember the specifics of that. Eric's article is long because the topic is tough, I struggle to think of a concise way to summarize his awesome work, but I think simple high-level explanations of any topic are always possible.

The latter half of your text about class being syntactic sugar also probably doesn't belong in the subsection about composition. We can definitely put that though in the preamble of the Classes section!

Action items: let's discuss if we need to be providing a summary of these articles. If we determine we do need to, can we explain this as absolutely simply as possible? The explanation here is good if you already know the concepts, but it doesn't help novices fully.

tk-o commented Jan 14, 2017

Copy link
Copy Markdown
Author

Thank you @ryanmcdermott for your review!

The key point for me was to show how to compose different objects into prototype of the particular one. The other point was to encourage the developer to not use classes for inheritance (as JS does not provide real classes which are known from languages like C# or Java) and that is why I mentioned about class being syntactic sugar.

A summary of the articles is there to briefly explain why the first example of code is bad and why the second one is better. Of course we can skip it if it may be confusing for some developers. Although we don't know if person reading this documentation has experience from other object-oriented languages or not. Therefore I believe the should some context provided on why we use factory function instead of class constructor.

Copy link
Copy Markdown

Why should you not use ES6 classes for classical inheritance?

ProLoser commented Jan 17, 2017
edited
Loading

Copy link
Copy Markdown

I think I have to completely disagree with this entire edit.

You've taken out completely the discussion of why composition is more relevant vs inheritence and replaced it with an extremely biased blanket statement that "js inheritence is prototypal under the hood, therefore you should not use it." And then directed them off-site.

Why does it matter if under the hood it's done using prototypes? What is the reason NOT to use it? You've only added material on explaining how it works but in my experience that isn't a reason in-of-itself to NOT use it. As with any language, it's a tool that you may use if it makes sense.

The original text went into how to discern when such a scenario is present and how to quickly identify it. The original text also would have made sense with this addendum which displays how both can be used together:

class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

class Person {
  constructor(name, email) {
    this.name = name;
    this.email = email;
}

class Employee extends Person {
  constructor(employeeId) {
    this.employeeId = employeeId;
  }

  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}

It just kinda seems like you don't like the new ES6 feature personally.

tk-o commented Jan 17, 2017
edited
Loading

Copy link
Copy Markdown
Author

Hey @ProLoser, thanks for your point of view. I will try to justify mine.
Long story short: I was about to encourage the novice JS developer to understand JS inheritance mechanism under the hood (as we are talking about clean code in JS).

The section title is saying "Prefer composition over inheritance" so I was trying to show this kind of preference. Of course classes would be natural choice for developers who already code in Java, C# etc. And they don't always know that JS classes are not real ones.

To sum up, I am not saying "you can't use classes in JS". But when you use them know their limitations well (plus know that composition is not restricted by these limitations).

ProLoser commented Jan 17, 2017
edited
Loading

Copy link
Copy Markdown

But why did you delete all the prior information that so clearly helped people understand when they should use it?

For example I found this very helpful:

  1. Your inheritance represents an "is-a" relationship and not a "has-a" relationship (Animal->Human vs. User->UserDetails).
  2. You can reuse code from the base classes (Humans can move like all animals).
  3. You want to make global changes to derived classes by changing a base class. (Change the caloric expenditure of all animals when they move).

ProLoser commented Jan 17, 2017
edited
Loading

Copy link
Copy Markdown

I think I may just fundamentally disagree with you on this one, because I personally think your example would be better off done using classes and inheritence syntax.

I may be in the minority on this, so if there's a general majority that would agree with you I'll digress, but I'm curious if anyone else could chime in? I added an up/down vote to the original comment to perhaps see what other people thought.

I do agree with a few points the article makes, but overall I don't think it's correct.

For instance, exposing a public API for inheritence is bad, you shouldn't have to extend classes to use a library. But it's completely reasonable to extend classes in a library to extend the library (such as creating plugins).

// CONSUMING THE LIBRARY WITH COMPOSITION:
const Telemetry = require('telemetry');
const telemetry = new Telemetry();
const ga = new Telemetry.GoogleAnalyticsPlugin();

// Composing the library
telemetry.debug(true);
telemetry.addUser({ /** currentUser **/ });
telemetry.setEnvironment('production');

// Composing the plugin
ga.setAppId('googleAnalyticsId');
ga.setProject('myProject');
telemetry.register(ga);


// EXTENDING THE LIBRARY WITH INHERITENCE:
const ComScore = require('comscore');
class ComScorePlugin extends Telemetry.BasePlugin {
  constructor() {
    super();
    this.api = new ComScore();
  }
  dispatch(event) {
    this.api.send(event);
  }
}

It's just not black and white.

Copy link
Copy Markdown

I almost feel this entire heading should be changed. People should make sure they identify when something should be a subclass vs a mixin. And another bullet that discusses explicit composeable code vs implicit convention-based code. Inheritence is just a tool which (like any tool in coding) can be used well or abused.

tk-o commented Jan 17, 2017

Copy link
Copy Markdown
Author

I think each of our two has made his point. We have provided quite wide picture on the topic so I would be happy if more developer would leave their comments here.

caesarsol commented Jan 19, 2017
edited
Loading

Copy link
Copy Markdown

I think that the numbered list that @ProLoser points to should be kept and maybe expanded on, it's clear and easy to read for novices.

The proposed additions by @kopacki contains too many links IMHO, a novice could be overwhelmed by having links to five different articles. (I also think Eric Elliott is a bit too extreme on hating classes 😁 )
Besides, the document very rarely links to other articles, so it's not consistent.

A simple solution would be to summarize the content of every article, and I think adding some concept from the Clojure/Lisp world could help:

"It is better to have 100 functions operate on one data structure than to have 10 functions operate on 10 data structures." - Alan J. Perlis

Also: classes are fine, the very bad thing to do is a chain of inheritance 5 levels deep. That's the devil!

ericelliott commented Jan 20, 2017
edited
Loading

Copy link
Copy Markdown

Perhaps a mention of the problems associated with class inheritance would be instructive. In particular:

  • The gorilla/banana problem
  • The fragile base class problem
  • The duplication by necessity problem (caused by the fragile base class problem)

Explain why composition is immune to these problems.

@ProLoser Given enough time and evolution, all class taxonomies are wrong for new use-cases. For example, say you have a base-class Animal which implements .move() as described in the original examples.

Not all animals move in the sense that snakes and monkeys move. Some are entirely incapable of locomotion. What happens when you need to add barnacles, corals, and sea anemones?

You need to either change the base class, create a new base class that sessile animals can inherit from instead of the original Animal base-class. Both the old Animal class and the new sessile class can inherit from the new base-class, OR add special-case code for sessile animals everywhere any Animal instance is used to account for the differences between Animals and Sessile Animals.

With composition, this problem does not exist. Adding new use-cases is simply a matter of composing different feature sets.

ericelliott commented Jan 20, 2017
edited
Loading

Copy link
Copy Markdown

@caesarsol I have good reasons to dislike class inheritance. I've seen it cause lots of expensive rewrites, and got a first-hand up-close look at how it can cause entire companies to fail. See "Inside the Dev Team Death Spiral".

Copy link
Copy Markdown

@ericelliott I absolutely agree with you! I've seen some mild cases of what you describe, and hated it.
That was just a reference to the abundance of your articles on the issue. :)

By the way I like your "Animal.move()" explanation very much, It's easy language and I'm sure would be very helpful for novices. The important thing is to foresee the future, not to just make things working now.

Copy link
Copy Markdown
Owner

Thanks @ericelliott for your incredible advice as always! My preference here is to just link out to articles with a one sentence description explaining it's important to understand prototypal inheritance. We should take the second example and put it in the preamble of the Classes section. I'll leave this PR in case you had the time to refactor this. Thanks all for contributing to this 😄

Voronar commented Feb 16, 2018

Copy link
Copy Markdown

This section uses an explicit dependency EmployeeTaxData that violates the Dependency Inversion Principle.

Copy link
Copy Markdown
Owner

Closing due to inactivity, feel free to re-open!

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants


Back | FazBrowse Home | New Git URL