Skip to content

Commit

Permalink
Add some links
Browse files Browse the repository at this point in the history
  • Loading branch information
sapegin committed Jun 30, 2024
1 parent bee650e commit 1d3dc27
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 15 deletions.
5 changes: 4 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"autofixing",
"Baggins",
"Bundlephobia",
"catwell",
"Chapuis",
"Cheburashka",
"chucknorris",
"cochinita",
Expand Down Expand Up @@ -67,9 +69,9 @@
"polizei",
"preorder",
"preprocessors",
"Rauschmayer",
"refactorings",
"richtypo",
"Rauschmayer",
"Roomba",
"rubberducking",
"Sandi",
Expand Down Expand Up @@ -102,6 +104,7 @@
"xlarge",
"xxlarge",
"Zakharchenko",
"Zakirullin",
"zwei"
]
}
10 changes: 7 additions & 3 deletions manuscript/030_Avoid_conditions.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,9 @@ expect(onError).toBeCalledWith('nope')
expect(() => getRandomJoke(onDone)).not.toThrowError()
-->
Here, the `onError` parameter is optional, and we check if it exists before calling it. The problem here is that we need to remember to wrap each call to an optional callback with a condition. It increases complexity and cognitive load, and makes the code harder to read.
Here, the `onError` parameter is optional, and we check if it exists before calling it. The problem here is that we need to remember to wrap each call to an optional callback with a condition. It increases complexity and cognitive load (the mental effort required to understand the code) and makes the code harder to read.
I> Artem Zakirullin has a [great article on cognitive load in programming](https://github.com/zakirullin/cognitive-load).
One way to simplify the code here is by using the _optional chaining_ operator:
Expand Down Expand Up @@ -988,9 +990,9 @@ I> The [single responsibility principle](https://en.wikipedia.org/wiki/Single_re

## Tables or maps

One of my favorite techniques for improving _(read: avoiding)_ conditions is replacing them with tables or maps. With JavaScript, we can create a table or a map using a plain object.
One of my favorite techniques for improving _(read: avoiding)_ conditions is replacing them with _tables_ or _maps_. In JavaScript, we can create a table or a map using a plain object.

Weve just done this as a part of ourspecial offersrefactoring example above. Lets have a look at a simpler example now. This example may be a bit extreme, but I actually wrote this code 19 years ago:
Weve just done this as a part of ourspecial offersrefactoring example above. Lets have a look at a simpler example now. This example may be a bit extreme, but I actually wrote this code in my early twenties:

<!-- let month = 'may' -->

Expand Down Expand Up @@ -1396,6 +1398,8 @@ expect(getDateFormat()).toBe('M/D')
The improved version isn’t much shorter, but now it’s easy to see all date formats. We’ve extracted the data to a short and readable object and separated it from the code that accesses the right piece of this data.
I> There’s a proposal to add [pattern matching](https://github.com/tc39/proposal-pattern-matching) to JavaScript, which may give us another option: more flexible than tables but still readable.
Here’s one more example:
```js
Expand Down
6 changes: 1 addition & 5 deletions manuscript/060_Avoid_comments.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,7 @@ expect(test.test('Mac_PowerPC', 'Mozilla/4.0 (compatible; MSIE 5.17; Mac_PowerPC
expect(test.test('MacInter', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50')).toBe(false)
-->

Now, the condition is shorter and more readable, because names help us to understand what the condition does in the context of the code.

However, I don’t think that splitting a linear algorithm, even a long one, into several functions, and then calling them one after another, makes code more readable. Jumping between functions (and even more so — files) is harder than scrolling, and if we have to look into functions’ implementations to understand the code, then the abstraction wasn’t the right one. Naming could be a problem too when all the extracted functions are parts of the same algorithm.

Overall, I don’t like when the code is measured by its physical metrics, like the number of lines. Long functions aren’t always hard to read and modify, And the really complex code could be tiny.
Now, the condition is shorter and more readable, because names help us to understand what the condition does in the context of the code. However, I don’t think that splitting a function into multiple just because it’s “long” makes the code more readable.

I> We talk about code splitting in more detail in the [Divide and conquer, or merge and relax](#divide) chapter.

Expand Down
14 changes: 10 additions & 4 deletions manuscript/080_Divide_and_conquer.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ It’s hard to manage shared code in large projects with many developers and tea

Imagine team A is adding a comment form to their page: a name, a message, and a submit button. Then team B needs a feedback form, so they find team A’s component and try to reuse it. Then team A also wants an email field, but they don’t know that team B uses their component, so they add a required email field and break the feature for team A users. Then team B needs a phone number field, but they know that team A is using the component without it, so they add an option to show a phone number field. A year later, two teams hate each other for breaking each other’s code, and a component is full of conditions and is impossible to maintain. Both teams would save a lot of time and have healthier relationships with each other if they maintained separate components composed of lower-lever shared components, like an input field or a button.

T> It might be a good idea to forbid other teams to use our code unless it’s designed and marked as shared. The [Dependency cruiser](https://github.com/sverweij/dependency-cruiser) is a tool that could help set up such rules on a project.

Sometimes, we have to roll back an abstraction. When we start adding conditions and options, we should ask ourselves: is it still a variation of the same thing or a new thing that should be separated? Adding too many conditions and parameters to a module can make the API hard to use and the code hard to maintain and test.

Duplication is cheaper and healthier than the wrong abstraction.
Expand All @@ -34,7 +36,11 @@ The higher level of the code is, the longer we should wait before we abstract it

_Code reuse_ isn’t the only or even most important reason to extract a piece of code into a separate function or module.

_Code length_ is often [used as a metric](https://softwareengineering.stackexchange.com/questions/27798/what-is-proven-as-a-good-maximum-length-of-a-function) when we should split a module or a function, but size alone doesn’t make code hard to read or maintain, and often splitting code into many teeny-tiny functions makes it harder to read and modify.
_Code length_ is often [used as a metric](https://softwareengineering.stackexchange.com/questions/27798/what-is-proven-as-a-good-maximum-length-of-a-function) when we should split a module or a function, but size alone doesn’t make code hard to read or maintain.

Splitting a linear algorithm, even a long one, into several functions, and then calling them one after another, rarely makes code more readable. Jumping between functions (and even more so — files) is harder than scrolling, and if we have to look into functions’ implementations to understand the code, then the abstraction wasn’t the right one. Naming could be a problem too when all the extracted functions are parts of the same algorithm.

I> Pierre “catwell” Chapuis has [a good example](https://blog.separateconcerns.com/2023-09-11-linear-code.html) of splitting a function gone wrong.

You probably won’t find a lot of small functions in my code. In my experience, the most useful reasons to split code are _change frequency_ and _change reason_.

Expand Down Expand Up @@ -71,6 +77,8 @@ The same applies to functions that are meant to be used only together with a cer

Another benefit is that when we delete a module, we automatically delete its dependencies. Code in shared modules often stays in the codebase forever because it’s often hard to know whether it’s used anywhere or not (TypeScript makes it much easier, though).

I> Such modules are sometimes called _deep modules_: a relatively large module that encapsulates a complex problem, but has a simple API. The opposite of deep modules are _shallow modules_: many small modules that need to interact to each other.

If we often have to change several modules or functions at the same time, it may be better to merge them into a single module or function. This is sometimes called _colocation_.

A couple of examples of colocation:
Expand All @@ -82,12 +90,10 @@ I> Kent C. Dodds has [a nice article on colocation](https://kentcdodds.com/blog/

A common complaint about this approach is that it makes components too large. If that’s the case, it’s better to extract some parts into their own components, together with their markup, styles and logic.

The idea of colocation also conflicts with _separation of concerns_: an outdated idea that led web developers to keep HTML, CSS, and JavaScript in separate files (and often in separate folders) for too long and made us edit three files at the same time to make even the most basic changes on web pages.
The idea of colocation also conflicts with _separation of concerns_: an outdated idea that led web developers to keep HTML, CSS, and JavaScript in separate files (and often in separate parts of the code tree) for too long and made us edit three files at the same time to make even the most basic changes on web pages.

I> The _change reason_ is also known as the [single responsibility principle](https://en.wikipedia.org/wiki/Single_responsibility_principle): “every module, class, or function should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.”

T> It might be a good idea to forbid other teams to use our code unless it’s designed and marked as shared. The [Dependency cruiser](https://github.com/sverweij/dependency-cruiser) is a tool that could help set up such rules on a project.

## Sweep that ugly code under the rug

Sometimes, we have to use an API that’s especially difficult to use or error-prone. For example, it requires several steps in a particular order with particular parameters that are always the same. This is a good reason to create a utility function to make sure we always do it right. As a bonus, we could now write tests for this piece of code.
Expand Down
4 changes: 2 additions & 2 deletions manuscript/090_Dont_make_me_think.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Some people confuse _brevity_ with _clarity_. Short code (brevity) isn’t alway

<!-- textlint-disable alex -->

There are many ways to express the same idea in the code. However, some of them are easier to understand then others. We should always try to reduce the cognitive load, the mental effort required to understand the code, of the next developer who’ll read out code. Every time we stumble on something that isn’t immediately obvious, we waste our brain’s resources.
There are many ways to express the same idea in the code. However, some of them are easier to understand then others. We should always try to reduce the cognitive load of the next developer who’ll read out code. Every time we stumble on something that isn’t immediately obvious, we waste our brain’s resources.

<!-- textlint-enable -->

Expand Down Expand Up @@ -214,7 +214,7 @@ const obj = {
<!-- expect(obj).toEqual({ value: 42 }) -->
I usually prefer when object don’t change their shapes, so I’d move the condition inside the `value` field:
I usually prefer when objects don’t change their shapes, so I’d move the condition inside the `value` field:
<!-- const condition = true -->
Expand Down
3 changes: 3 additions & 0 deletions manuscript/160_Footer.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ Also remember this: _code is evil_. Our job isn’t writing code but solving our
- [Clever code considered harmful](https://www.joshwcomeau.com/career/clever-code-considered-harmful/) by Josh Comeau
- [Code Health: Reduce Nesting, Reduce Complexity](https://testing.googleblog.com/2017/06/code-health-reduce-nesting-reduce.html?m=1) by Elliott Karpilovsky
- [Code Health: To Comment or Not to Comment?](https://testing.googleblog.com/2017/07/code-health-to-comment-or-not-to-comment.html?m=1) by Dori Reuveni and Kevin Bourrillion
- [Cognitive Load is what matters](https://github.com/zakirullin/cognitive-load) by Artem Zakirullin
- [Colocation](https://kentcdodds.com/blog/colocation) by Kent C. Dodds
- [Everything is a Component](https://medium.com/@level_out/everything-is-a-component-cf9f469ad981) by Luke Hedger
- [Implicit Assertions](https://www.epicweb.dev/implicit-assertions) by Artem Zakharchenko
- [Is High Quality Software Worth the Cost?](https://martinfowler.com/articles/is-quality-worth-cost.html) by Martin Fowler
- [It’s probably time to stop recommending Clean Code](https://qntm.org/clean) by qntm
- [John Carmack on Inlined Code](http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html)
- [Learning Code Readability](https://medium.com/@egonelbre/learning-code-readability-a80e311d3a20) by Egon Elbre
- [Linear code is more readable](https://blog.separateconcerns.com/2023-09-11-linear-code.html) by Pierre “catwell” Chapuis
- [Making Wrong Code Look Wrong](https://www.joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong/) by Joel Spolsky
- [Modern React Testing](https://sapegin.me/blog/react-testing-1-best-practices/) by Artem Sapegin
- [Naming conventions in programming — a review of scientific literature](https://makimo.com/blog/scientific-perspective-on-naming-in-programming/) by Iwo Herka
Expand Down

0 comments on commit 1d3dc27

Please # to comment.