Skip to content

Instantly share code, notes, and snippets.

@cjcenizal
Last active December 3, 2015 23:03
Show Gist options
  • Save cjcenizal/7436b8d3f2a5a4b38a52 to your computer and use it in GitHub Desktop.
Save cjcenizal/7436b8d3f2a5a4b38a52 to your computer and use it in GitHub Desktop.
How I think about code

How I think about code

Invert control to simplify Components

We can use composition to invert control in our Components. This just means that instead of making a Component responsible for knowing about and rendering its own sub-Components, we provide it with sub-Components as children or props.

For example, imagine we have a Panel Component. It renders a box, with any kind of content inside of it (a form, a list, whatever). Instead of having it render different content depending on flag props like isList or isForm, we can just pass in a Form Component or List Component as its children prop.

From the Wikipedia page:

Inversion of control serves the following design purposes:

  • To decouple the execution of a task from implementation.
  • To focus a module on the task it is designed for.
  • To free modules from assumptions about how other systems do what they do and instead rely on contracts.
  • To prevent side effects when replacing a module.

We can think of each of these points in the context of Components:

  • The rendered output of a Component is decoupled from the Component's code (the panel can be rendered as a form panel or a list panel, but the code for the Panel Component is unrelated to that functionality).
  • The Component's code is then focused on its single responsibility (the Panel Component is focused on being a panel, and nothing more).
  • The Component's props provide a contract about how it will function (the Panel Component accepts children, which it wraps inside a panel).
  • If we want to swap out a list panel for a list modal, we can just pass the list into a Modal component instead of a Panel component, and it will just work.

Encapsulate concerns and abstract away complexity

I think these two heuristics will generally help me end up with well-defined modules which adhere to SRP.

Encapsulation

It's OK if code gets a little messy inside of a file. It's more important to me that all of the code in a file is related under a single, understandable concern. It's like the difference between having all of your clothes in a box, vs having them in a dresser with your socks in one drawer and your underwear in another. The contents of the drawers themselves might be messed up, but at least you know where to look to find something and how the drawers relate to one another.

Abstraction

The interface of a file or Component should make it easy for you to understand what the file is doing, even if you don't know exactly how it's doing it. When you press the handle on a toilet, the poop goes away. You don't need to know that the handle is lifting a seal, which releases water from the tank into the bowl, and the difference in pressure between the air in the sewer line and the weight of the water in the bowl causes the water to push all your poop into the sewer. Ain't nobody got time for that.

Encourage mental models with interfaces

This is kind of artsy-fartsy, but a well-designed interface can help the user form a useful mental model of the code. For example, imagine a Collection class. Developers need to be able to add items to the collection.

// Really bad - I have to know that items is a property,
// and that it's an array.
const collection = new Collection();
collection.items.push(new Item());

// Less bad - I have to know that getItems returns a
// reference to the array inside the collection.
const collection = new Collection();
const items = collection.getItems();
items.push(new Item());

// Almost good - I don't have to know about any of
// those things but the method name is confusing.
const collection = new Collection();
collection.push(new Item());

// Good - The method name is clear.
const collection = new Collection();
collection.addItem(new Item());

The last way is good because it encourages you to develop an idea of what Collections do and what they are, on an abstract level. The other ways are bad because you're forced into thinking of a Collection in terms of its internals.

Monoliths aren't always bad

A single million-line file is difficult to understand. That is bad.

Two 500,000-line files are difficult to understand. That is bad.

Four 250,000-line files are difficult to understand. That is bad.

...One million single-line files are difficult to understand. That is bad.

Somewhere in the middle there's a sweet-spot, but it will vary from file to file. I think the only conclusion I can make regarding length of files is to ignore it. For me, "goodness" is determined by how understandable/readable/maintainable code is, not by file size.

I think I should use other heuristics (e.g. single-responsibility principle, abstraction, encapsulation) for deciding when to modularize.

Note: The above ideas also refer to method size, variable name length, and config option size. Bascially anywhere size might be an issue.

In the context of Components

Given a particular Component, can it be broken down into sub-Components? What are the top-level sub-Components? Can they be broken down further into sub-sub-Components?

Are more layers are introduced, the Components gets more complex and difficult to understand. I try not to go any deeper than I have to.

If a Components get very complex, with sub-, sub-sub-, and sub-sub-sub-Components, then I look for ways to simplify (i.e. reduce the depth of the Component tree):

  • Maybe I've over-generalized. Can any sub-sub-sub-Components be combined with a sub-sub-Component?
  • Maybe I've over-scoped. Would any of these sub-Components work as separate Components, without specifically belonging to the root Component in question?
  • By the same token, perhaps some sub-sub-sub-Components should really just be sub-sub-Components. If the relationship amongst them would still be easy to understand, then they should be.

Data structures and their relationships before code

"Bad programmers worry about the code. Good programmers worry about data structures and their relationships." - Linus Torvalds

Linus is a jerk, so I don't think his distinction between "good" and "bad" programmers is useful in this quote. But he's right about the importance of data structures and their relationships.

I think these closely relate to how well I understand and can solve the domain of the problem.

  • I need to understand the problem's domain in order to build a good solution.
  • With clear data structures and relationships, another developer can get an idea of the domain just by reviewing them.
  • Data structures that accurately represent the domain make the problem easier to solve.

With the right data structures, I think many problems can be boiled down to manipulating and comparing collections. I see this pattern again and again.

Type-checking benefits

If you have data structures defined in classes, then you can use PropTypes to specify how your Components interact with these classes. This can make the flow of data through a component easier to follow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment