Skip to content

Commit

Permalink
Tweaks from Dr. Taco, third order 🌮
Browse files Browse the repository at this point in the history
  • Loading branch information
sapegin committed Oct 4, 2024
1 parent 647378e commit d9d96ad
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 12 deletions.
2 changes: 2 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@
"Elbre",
"epub",
"eqeqeq",
"favs",
"Fili",
"Flexbox",
"Formik",
"garrofĂł",
"Gollum",
"greppability",
"greppable",
Expand Down
25 changes: 19 additions & 6 deletions manuscript/070_Naming_is_hard.md
Original file line number Diff line number Diff line change
Expand Up @@ -1649,6 +1649,8 @@ console.log(loud_fruits);
<!-- expect(loud_fruits).toEqual(['GUAVA', 'PAPAYA', 'PINEAPPLE']) -->
Note the use of different naming conventions: `loud_fruits` uses snake_case, and `toUpperCase` uses camelCase.
Now, compare it with the same code using camelCase:
<!-- let console = { log: vi.fn() } -->
Expand All @@ -1662,6 +1664,8 @@ console.log(loudFruits);
<!-- expect(loudFruits).toEqual(['GUAVA', 'PAPAYA', 'PINEAPPLE']) -->
Since JavaScript’s own methods and browser APIs all use camelCase (for example, `forEach()`, `toUpperCase()`, or `scrollIntoView()`), using camelCase for our own variables and functions feels natural.
However, in Python, where snake_case is common, it looks natural:
```python
Expand All @@ -1670,8 +1674,6 @@ loud_fruits = [fruit.upper() for fruit in fruits]
print(loud_fruits)
```
Also, JavaScript’s own methods, and browser APIs are all using camelCase: `forEach()`, `toUpperCase()`, `scrollIntoView()`, and so on.
One thing that developers often disagree on is how to spell acronyms (for example, HTML) and words with unusual casing (for example, iOS). There are several approaches:
- Keep the original spelling: `dangerouslySetInnerHTML`, <!-- cspell:disable -->`WebiOS`<!-- cspell:enable -->;
Expand All @@ -1680,7 +1682,7 @@ One thing that developers often disagree on is how to spell acronyms (for exampl
Unfortunately, the most readable approach, normalization, seems to be the least popular. Since we can’t use spaces in names, it can be hard to separate words: <!-- cspell:disable -->`WebiOS`<!-- cspell:enable --> could be read as <!-- cspell:disable -->`webi os`<!-- cspell:enable --> instead of `web ios`, and it takes extra time to read it correctly. Such names also don’t work well with code spell checkers: they mark <!-- cspell:disable -->`webi`<!-- cspell:enable --> and <!-- cspell:disable -->`htmlhr`<!-- cspell:enable --> as incorrect words.
The normalized spelling doesn’t have these issues: `dangerouslySetInnerHtml`, `WebIos`, `XmlHttpRequest`, `DatePickerIos`, `HtmlHrElement`.
The normalized spelling doesn’t have these issues: `dangerouslySetInnerHtml`, `WebIos`, `XmlHttpRequest`, `DatePickerIos`, or `HtmlHrElement`. The word boundaries are clear.
## Avoid unnecessary variables
Expand Down Expand Up @@ -1779,7 +1781,7 @@ test(document2)
expect(document2.documentElement.className).toBe(' trans')
-->
In the code above, the alias `b` replaces a clear name `document.body.style` with not just an obscure one but misleading: `b` and `styles` are unrelated. Inlining makes the code too long because the style values are accessed many time, but having a clearer shortcut would help a lot:
In the code above, the alias `b` replaces a clear name `document.body.style` with not just an obscure one but misleading: `b` and `styles` are unrelated. Inlining makes the code too long because the style values are accessed many times, but having a clearer shortcut would help a lot:
<!-- function test(document) { -->
Expand Down Expand Up @@ -1961,15 +1963,26 @@ const {container: c1} = RTL.render(<Tip type="pizza" content="Hola" />);
expect(c1.textContent).toEqual('Hola')
-->
Another good reason to use an intermediate variable is to split a long line of code into multiple lines:
Another good reason to use an intermediate variable is to split a long line of code into multiple lines. Consider this example of an SVG image stored as a CSS URL:
```js
const borderImage = `url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><path d='M2 2h2v2H2zM4 0h2v2H4zM10 4h2v2h-2zM0 4h2v2H0zM6 0h2v2H6zM8 2h2v2H8zM8 8h2v2H8zM6 10h2v2H6zM0 6h2v2H0zM10 6h2v2h-2zM4 10h2v2H4zM2 8h2v2H2z' fill='%23000'/></svg>")`;
```
<!-- expect(borderImage).toMatch('<svg ') -->
Lack of formatting makes it hard to read and modify. Let’s split it into several variables:
```js
const borderSvg = `<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><path d='M2 2h2v2H2zM4 0h2v2H4zM10 4h2v2h-2zM0 4h2v2H0zM6 0h2v2H6zM8 2h2v2H8zM8 8h2v2H8zM6 10h2v2H6zM0 6h2v2H0zM10 6h2v2h-2zM4 10h2v2H4zM2 8h2v2H2z' fill='%23000'/></svg>`;
const borderPath = `M2 2h2v2H2zM4 0h2v2H4zM10 4h2v2h-2zM0 4h2v2H0zM6 0h2v2H6zM8 2h2v2H8zM8 8h2v2H8zM6 10h2v2H6zM0 6h2v2H0zM10 6h2v2h-2zM4 10h2v2H4zM2 8h2v2H2z`;
const borderSvg = `<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><path d='${borderPath}' fill='%23000'/></svg>`;
const borderImage = `url("data:image/svg+xml,${borderSvg}")`;
```
<!-- expect(borderImage).toMatch('<svg ') -->
While there’s still some line wrapping, it’s now easier to see the separate parts the image is composed of.
## Avoiding name clashes
We’ve talked about avoiding number suffixes by making names more precise. Now, let’s explore a few other cases of clashing names and [how to avoid them](https://gist.github.com/sapegin/a46ab46cdd4d6b5045027d120b9c967d).
Expand Down
12 changes: 6 additions & 6 deletions manuscript/080_Divide_and_conquer.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

<!-- description: Splitting code into functions and modules, when the right time is to introduce an abstraction, and when it’s better to sleep on it -->

<!-- cspell:ignore favs -->

It’s nice to have a global button component, but if it’s too flexible and has a dozen boolean props to switch between different variations, it will be difficult to use. However, if it’s too rigid, developers will create their own button components instead of using a shared one.
Knowing how to organize code into modules or functions, and when the right time is to introduce an abstraction instead of duplicating code, is an important skill. Writing generic code that others can effectively use is yet another skill. There are just as many reasons to split the code as there are to keep it together. In this chapter, we’ll discuss some of these reasons.

{#grow-abstractions}

Expand Down Expand Up @@ -342,6 +340,8 @@ expect(pizza).toEqual({

This is already much better than the split version. An even better solution would be improving the APIs and making the code more clear. Pierre suggests that preheating the oven shouldn’t be part of the `createPizza()` function (and baking many pizzas myself, I totally agree!) because in real life the oven is already there and probably already hot from the previous pizza. Pierre also suggests that the function should return the box, not the pizza, because in the original code, the box kind of disappears after all the slicing and packaging magic, and we end up with the sliced pizza in our hands.

There are many ways to cook a pizza, just as there are many ways to code a problem. The result may look the same, but some solutions are easier to understand, modify, reuse, and delete than others.

Naming can also be a problem too when all the extracted functions are parts of the same algorithm. We need to invent names that are clearer than the code and shorter than comments — not an easy task.

I> We talk about commenting code in the [Avoid comments](#no-comments) chapter, and about naming in the [Naming is hard](#naming) chapter.
Expand Down Expand Up @@ -486,7 +486,7 @@ Here’s how the file tree changes with colocation:
| `src/actionCreators/feature.js` | |
| `src/reducers/feature.js` | |

I> Kent C. Dodds wrote [a nice article on colocation](https://kentcdodds.com/blog/colocation).
I> To learn more about colocation, read [Kent C. Dodds’s article](https://kentcdodds.com/blog/colocation).

A common complaint about colocation is that it makes components too large. In such cases, it’s better to extract some parts into their own components, along with the markup, styles, and logic.

Expand Down Expand Up @@ -759,8 +759,6 @@ The benefits are:
- **Reusability:** often, the “how” is generic, and we can reuse it, or even import it from a third-party library.
- **Testability:** each validation and the validation runner function are isolated, and we can test them separately.

There are many more examples of such refactorings in this book.

{#monster-utilities}

## Avoid monster utility files
Expand Down Expand Up @@ -1148,6 +1146,8 @@ I> Check out my [Jest](https://github.com/sapegin/jest-cheat-sheet) and [Vitest]

The biggest challenge with abstractions is finding a balance between being too rigid and too flexible, and knowing when to start abstracting things and when to stop. It’s often worth waiting to see if we really need to abstract something — many times, it’s better not to.

It’s nice to have a global button component, but if it’s too flexible and has a dozen boolean props to switch between different variations, it will be difficult to use. However, if it’s too rigid, developers will create their own button components instead of using a shared one.

We should be vigilant about letting others reuse our code. Too often, this creates tight coupling between parts of the codebase that should be independent, slowing down development and leading to bugs.

Start thinking about:
Expand Down

0 comments on commit d9d96ad

Please sign in to comment.