「Tidy First?」の Part Ⅰ を読んだ

2024/02/16に公開

https://www.oreilly.com/library/view/tidy-first/9781098151232/

「Tidy First?」 はKent Beck が新しく出した本です。
「Kent Beck is 誰?」というのは人によって答えがちがうと思いますが、一般的には XP を作った人、私の中では JUnit を作った人、という印象です。

Tidyings?

「tidy」「几帳面」「綺麗好き」 みたいな意味合いがある言葉です。
「tidying」 も普通に名詞化しているようで、「きれいな状態にすること」「お片付けをすること」 という意味になりそうです。

https://en.wiktionary.org/wiki/tidying

The act or process in which things are tidied.

「Tidy First?」 はソフトウェア開発に関する本なので、この本の中での意味は以下です。

  • Tidyings は Refactoring と同じようなもの。 Refactoring の Subset
  • Tidyings は、とても小さな Refactoring
  • 反対が少なく、賛同を得やすい修正

TIdyings are the cute, fuzzy little refactroings that nobady could passibly hate on.

Kent Beck が指す 「Tidyings」 とはどのようなものなのか、本書の中でも「Part Ⅰ」の中で具体例が詳説されています。

なお、当書 「Tidy First?」は、以下のような章立てになっており、Part Ⅱ 〜 Part Ⅲ により面白い内容が記述されている印象があります。そのこともあり、当書のレビューを見て回ると、概ね Part Ⅱ・Ⅲ に言及していることが多いようです。

ただ、この記事では 「Tidyings」 の雰囲気を掴むため、Part Ⅰ の内容について記載していきます。

  • Part Ⅰ - Tidyings: 具体的な Tidyings の方法・手法
  • Part Ⅱ - Managing
  • Part Ⅲ - Theory

Chapter 1. Guard Clauses

https://tidyfirst.substack.com/p/guard-clause

Guard Clauses は有名な言葉ですね。「ガード構文」等と言われることが多いです。
IF などにより条件分岐がある際は、構造を複雑にネスト化するのではなく、早めに return や break できるものをできるだけ上に記載し、できるだけシンプルな形にする、というテクニックです。

具体例を見たほうが早そうです。

if (condition)
  if (not other condition)
    ... some code ...

という書き方より

if (not condition) return
if (other condition) return
  ... some code ...

という風に書いたほうが見やすいでしょう、という手法です。

ただし、 guard clause はあまりやりすぎても見づらくなるので駄目、とも書いてます。可読性や認知負荷的にちょうど良いバランスにするのが大事そうです

Don't overdo guard clauses. A routine with seven or eight guard clauses is not easier to read. It needas more acute care to partition complexity.

Chapter 2. Dead Code

Daad Code は直感的に理解できると思います
プログラムの動作に不要なコードは保守性や悪く認知負荷あがるためできるだけ消すことが望まれます

Delete it. That's all. If the code dosen't get executed, just delete it.

Dead Code と判断して消したコードが、実際は必要なコードであった、というケースも発生し得ます。
なので、一斉に実施するというよりは、消し間違えてもすぐ戻せるよう、小さく小さく、几帳面に (tidy に) 不要なコードを消していくことが大事です。

Chapter 3. Normalize Symmetries

https://tidyfirst.substack.com/p/normalize-symmetries

プログラムは表現方法は1種類ではなく、同じ処理を行おうとしても書き方が複数あります。
ただ、皆が別々の書き方をしていると認知負荷が高まるため、ある程度対称性を正規化しましょう(書き方を揃えましょう)、という内容です。
具体的に記載されていた内容は以下。いずれも、同じ振る舞いをするコード、ということになっています

foo()
  return foo if foo not nil
  foo := ...
  return foo
  
foo()
  if foo is nil
    foo := ...
  return foo
  
# tricky
foo()
  return foo not nil
    ? foo
    : foo := ...

# double tricky, assuming assignment is an expression
foo()
  return foo := foo not nil
    ? foo
    : ...
    
# even trickier, hiding the conditional
foo() 
  return foo := foo || ...

上記はいずれも同じ振る舞いになる、という前提で、自然にまかせてしまうと皆様々な書き方をしてしまうため、正規化が必要、とのことのようです。この辺の書き方で独創性を発揮しても意味がないので、読みやすい書き方が望ましそうです。

Code grows organically. Some folk use "organic" as a pejorative. That make no sense to me.

上記の擬似コードのうち、私が書くとしたら一番上に近い書き方になりそうです。
実際は Java で書くなら Optional を使って以下のように書くと思います

return foo.orElse(...); //foo が null なら '...', not null なら foo の値が返る

コーディング規約や、IDE の formatter でルールを定めるのが有効なアプローチと思います。
下3つは、多くの IDE では警告が出ると思います。

Chapter 4. New Interface, Old Implementation

https://tidyfirst.substack.com/p/tidying-new-interface-old-implementation

実装があって、そのインターフェースがあって、インターフェースが理解が難しかったり複雑だったりする場合、

  • 実装はそのまま (Old Implementatino)
  • インターフェースだけ新しくする (New Interface)

というアプローチが有効だ、ということのようです。

ただ、書いた本人もこの内容についてはあまりすっきりしてないようで、上記引用した tidyfirst のページでは以下のように書いてあります。

[I'm likely to delete this tidying because it is rarer & less clear. If you find it helpful, lmk & I'll leave it in. I’m sending it out to all subscribers because I need an example.]

書籍にも、例文は掲載されてませんでした。
でも本にはするんだ? という小並感の感想を抱きました。

Chapter 5. Reading Order

https://tidyfirst.substack.com/p/examples-needed-reading-order-and

プログラムは人間が理解しやすいように、できる限り意味的に順番に並んでいることが望ましい、ということのようです。

本には例文が無かったですが、サイトでは以下のような例が載っていました

# old (bad)
write()
read()
process()
# new (bad)
read()
process()
write()

処理の流れとして、入力 → 処理 → 出力 という順番に行われるのが自然なので、関数の定義の順番もそのように並んでいた方がわかりやすい、という意味のようです。

Chapter 6. Cohesion Order

https://tidyfirst.substack.com/p/cohesion-order

Cohesion = 凝集、という意味。
意味的に関連があるものは、近い場所に書かれていた方が良い、という意味のようです。

「凝集する」というものの意味のスコープは、本文に書いてあった内容によると以下のようなものです

  • 同じクラスファイル内 (似たような処理は同じクラス内にいた方が良い)
  • 同じディレクトリ内 (似たような処理は同じディレクトリ内に揃っていた方が良い)
  • 同じリポジトリ (似たような処理は同じリポジトリにいた方が良い)

本文中に、 coupling (coupled) という言葉が多く出てきます。
これの意味が本を読んでいた時には正確に理解できてなかったのですが、以下のサイトを読んでなんとなく理解をしました。
https://www.infoq.com/presentations/refactoring-cleaning-code/

The definition of coupling, if I have two elements, E1 and E2, and I have a specific change that I want to make, some delta, this is defined as if I change element one, that implies I have to change element two also. That's the definition of coupling.

つまり、同じような処理を行うコードが複数箇所(E1、E2)に存在していると、修正をする時に、E1も直さないといけないし、E2も直さないといけない。
この場合における E1と E2 の関係が coupled である、ということのようです。

coupling / coupled の反対語は decoupling / decoupled

それぞれにおいて、修正が発生した際のコストがどういう関係になるか、以下のような数式で表現されていました。
端的に、coupling が多いコード(複数の箇所に同じような処理が書かれているコード)の方が、そうでないコードより修正コストが高い、ということになります。

cost(decoupling) + cost(change) < cost(coupling) + cost(change)

ソースコードの Coupling の度合いは、たとえば Code Climate などのツールを使うと自動的に測定することができます。
(Coupling している箇所は 「Similar blocks of code found~」 という形で指摘してくれます。)

Chapter 7. Move Declaration and Initialization Together

https://tidyfirst.substack.com/p/tidying-move-declaration-and-initialization

コードの宣言と初期化は一緒に行うべき、ということを指しています。よく言われる話ですね。

# bad
fn()
  int a
  ... some code that doesn't use a
  a = ...
  int b
  ... some more code, maybe it uses a but doesn't use b
  b = ...a...
  ... some code that uses b
# good
fn()
  int a = ...
  ... some code that doesn't use a
  ... some more code, maybe it uses a but doesn't use b
  int b = ...a...
  ... some code that uses b

Chapter 8. Explaining Variables

コードは最初は小さくても自然に巨大化するので、特定のコードシーケンスが最初は自明でもいずれ複雑化し、内容の理解が難しくなることがあります。

なので、変数などについては、それがどのような意味なのか、わかりやすい形にすることが大事、ということのようです。

コメントなどの付与も大事そうですが、

  • プログラムの構造
  • 変数や関数の意味の明瞭化

あたりが大事なポイントになりそうです。

本文に例として載っていたものを引用します

# bad
return new Point(
  ... big long expression ...,
  ... another big long expression ...
)
# good
x := ... big long expression ...
y := ...another big long expression ...
return new Point(x, b)

プログラム例では変数名が 「x」「y」 となっていますが、実際は「width」「height」、「top」「left」、「run」「rise」、といった形のようになるでしょう、と本文には書いてありました。

Chapter 9. Explaining Constants

これもよく言われるもので、マジックナンバー的な記述をするのではなく、

  • 定数を作成した方が良い
  • その名前も、わかりやすくて解釈のブレがないような名前にした方が良い

という話です。

# bad
if response.code = 404
  ... blah blah blah ...
# good
PAGE_NOT_FOUND := 404
if response.code = PAGE_NOT_FOUND
  ... blah blah blah ...

IDE によっては、マジックナンバー的な記述には自動的に警告が表示されるようなものも多い印象です。

Chapter 10. Explicit Parameters

https://tidyfirst.substack.com/p/explicit-parameters

メソッドなどに明示的にパラメータが渡されてない件について書かれています。
具体的には、メソッドにパラメータを渡す際には、変数を定義したり名前付き変数などを用るべきで、配列や連想配列をそのまま渡すようなコードは良くない、という話です。

# bad
params = {a: 1, b: 2}
foo(params)

function foo(params)
  ... params.a  ... ... params.b ...
# good
function foo(params)
  foo_body(params.a, params.b)

function foo_body(a, b)
  ... a  ... ... b ...

Chapter 11. Chunk Statements

https://tidyfirst.substack.com/p/tidying-chunk-statements

プログラムが複数行ずらーーーっと並んでいると見づらいので、キリの良いタイミングで改行を入れましょう、という提案です。
極めてシンプルだけど効果的、という事が書いてありました。

I like tha this tidying is so, so simple. That's part of the philosophy of "Tify First?"

Chapter 12. Extract Helper

https://tidyfirst.substack.com/p/tidying-extract-helper

コードの中で再利用性が高いセンテンスがある場合は、それを独立した処理として抜き出すべき、という話です。
いわゆる「Extract Method」で、リファクタリングの基本の「キ」の話で、よく語られる話ですね。

# before
routine()
  ... stuff that stays the same ...
  ... stuff that needs to change ...
  ... stuff that stays the same ...
# after
helper()
  ... stuff that needs to change ...

routine()
  ... stuff that stays the same ...
  helper()
  ... stuff that needs to change ...

Chapter 13. One Pile

https://tidyfirst.substack.com/p/tidying-one-pile

pile は、「積み重なったもの」「山」といった意味です。
https://eow.alc.co.jp/search?q=pile

ソースコードの記述は、あまりにも細かく分散されすぎているよりは、一つのクラスなどにインラインでまとめて記述されていた方が良い、という提案です。

では、適切な粒度にクラスを分割せずに、肥大化した巨大なクラスを作れば良い、という提案かというとそうではなく、一番の要点としては

  • ソースコードの一番のコストは、可読性の悪さ
  • 書きにくさではない

ということのようで、可読性の高いコードを書いていく必要がある、という提案のようです。

The biggest cost of code is the cost of reading and understanding it, not the cost of writting it.

小さすぎるコードは他のコードとの相互作用性が大きくなりがちで、可読性が落ちることがある、ということが、言いたいことのようです。

Chapter 14. Explaining Comments

適切なわかりやすいコメントを付与しましょう、という話です。

Chapter 15. Delete Redundant Comments

適切なコメントの付与は必要ですが、不要なコメントは削除しましょう、という話です。

一番あるケースとして、ソースコードを見れば何をしているか自明なのに、同じ内容を自然言語で説明している、というケースです。こういうのはムダなので削除しましょう、という提案のようです。

When you see a comment that says exactly what the code says, remove it.

例としては、さすがにこんなコードは無いだろう、というひどい例が載ってました。

getX()
  # return X
  return X

Conclusion

全体的に 「可読性」「コードを読むためにかかるコスト(cost of reading and understanding )」 に重点をおいている事が伺えます。

コードの可読性については、コードの品質を取り扱うサービスや本では常に重要視されており、 CodeClimate でもコードの品質の指標として 「Cognitive Complexity(認知的複雑度)」を重視しているようです
(見た目複雑で理解できないコードがあると、品質が低い、バグを生み出す温床になる、という判断)

We recommend checking for cognitive complexity rather than cyclomatic complexity, and we pair that with a return statements check, as two of our 10 point maintainability inspection. (All of which is completely configurable).

https://docs.codeclimate.com/docs/cyclomatic-complexity

コードの可読性を向上させるためのサポートとして、IDE でも上記のような記述についてはサポートが充実しており、基本的に可読性などに支障をきたすと思われるコードについては警告が自動的に表示されるようになっているものが多いです。

いきなり大きなリファクタリングはコストが高く実行が難しいですが、手がつけやすく、間違いが起こりづらい 「Tidyings」 を行うことで、今までのコードよりも「良いコード」に一歩近づくことができると思います。なので、この本に書いてあるようなアプローチは有益だと思います。

そして、 「Tidyings」 活動は、CodeClimate や IDE で自動的に指摘してくれる項目が多いので、こういうツールを最大限有効活用していくのも、大事な取り組みと思います。

「こんな細かいことばかり対応していても、重箱の隅をつつくだけで意味がない」「もっと大きなリファクタリングをしないと技術的負債はなくならない」とお思いの方も多いと思いますが、その辺に対しての Kent Beck なりの回答が、Part Ⅱ、ならびに Part Ⅲ に書かれています。

そこで触れられている内容は、他のレビューサイトなどでもまとめられています。当 zenn のスペースでも、時間があったら自分なりにまとめてみようと思います。

Discussion