oinume journal

Scratchpad of what I learned

2021年の振り返り

2021年の抱負 - oinume journalで年初に考えていた抱負がどうだったのか?を振り返ってみる。

習慣化

Habitifyというアプリを1年使い続けてみたけど、習慣化するものとそうじゃないものがあった。ある程度習慣化したものはiKnow(英単語)だけで、その他は習慣化しなかった。Habitify、通知してくれるのはいいんだけど習慣化するにはモチベーションも維持する必要があるので、その点ではあまり役に立たなかったように思う。何かいいアプリがあったら乗り換えたい。

というわけでこれは達成度20%ぐらい。

ReactでSPA作れるようになる

趣味で開発しているプロダクトで、ログイン後の動的なページはReact化が終わった。まだSPAにはできていないけど、React + React Queryを使って動的なページを作るのはできるようになったので、来年はGraphQL + Next.jsを導入してみたい。

英語

今年は6月にTOEICを受けて自己ベストを更新 & 800点超えしたのが地味に嬉しかった(この時の問題が異常に簡単だったという説もある)。来年も自己ベストを更新できるように頑張りたい。

英語のレッスン自体は週1でやっていたのだけど、DMM英会話時代からお世話になっていた先生が育休に入ってしまうということで3ヶ月ぐらい中断している。来年は再開してこれも習慣化したい。

アウトプット

月に1エントリは書くという目標だったけど、2,3,4,5,10月が達成できず。いつものように仕事が忙しくなってしまうとアウトプットが疎かになってしまうので、月初からちゃんとネタを仕込みつつアウトプットできるようにする。

ライブ

海外アーティストが来れないので、そっちは諦めて邦楽のライブに3回ほど参戦。本当はGWのビバラも行くはずだったんだけどコロナが怖くて断念。これがずっと心残りだったので、デルタ株がピークだったけど散々悩んだ挙句、死ぬ気で8/6にZeppまで足を運んだ。もはや生き甲斐がライブぐらいしかないので、不謹慎で危険な行為だとは思ったけど行って良かったと思う。神経すり減らしながらも爆音が鳴っている中で「生きてる」って感じがして、なんとも言えない感覚だった。

  • 8/6 Straightener & Dragon Ash @ Zepp Tokyo
  • 10/14 ROTTENGRAFFTY @ Zepp DiverCity
  • 12/28 Creepy Nuts & Dragon Ash @ Zepp Tokyo

まとめ

2021年もパンデミックであまり旅行も行けなかったけど、ライブにも映画にも行けたし2020年よりはパンデミックに慣れてきた気がする。2022年はライブハウスでモッシュとかダイブができる世の中になるといいなぁと思っている。海外旅行も行きたいですね。

Chapter 4 - Modules Should Be Deep / A Philosophy of Software Design

第4章はModules Should Be Deepというタイトル。

4.1 Modular Design

  • ソフトウェアの複雑性を管理するためのもっとも大事なテクニックの一つとして、全体の複雑性の一部分だけに直面するようにシステムを設計するということが挙げられる。このアプローチは modular design と呼ばれている。
  • この手法を用いると、ソフトウェアシステムは複数のモジュール(クラス, サブシステム, サービス)に分解される。
  • それぞれのモジュールは相互に依存せず独立しているため、開発者はあるモジュールの開発をしている時に他のモジュールのことを考えなくて良い
  • しかし、上記の独立性の話はあくまで理想で実際にはそんなことはない
    • 例えば、あるメソッドに引数を追加した場合はその呼び出し元のコードも変更しないといけない
  • 依存の認識と管理をするためには、モジュールをinterfaceとimplementationの2つに分けることが大事である
  • Typically, the interface describes what the module does but not how it does it.
    • interfaceはそのモジュールが「何をするか」を表明し、それを「どうやるか」については表明しない
  • あるモジュールの開発者は、そのモジュールのinterfaceと実装、およびそのモジュールが依存している他のモジュールのinterfaceを知っている必要があるが、他のモジュールの実装については知らなくても大丈夫である
  • interfaceが実装に比べてとてもシンプルに表現されているモジュールがベスト
  • 上記のようなモジュールは2つのメリットがある
    • シンプルなインタフェースを提供するモジュールは、他のモジュールにもたらす複雑性を少なくする
    • インターフェースが変更されない限り、他のモジュールには影響がない。

4.2 What's in an Interface?

  • interfaceにはformal, informalな情報がある
  • formal: メソッドのシグネチャ(引数およびその型、戻り値およびその型)
    • プログラミング言語の機能によってもたらされる
  • informal: そのメソッドを呼び出した時にもたらされる結果など
    • eg) 引数で与えられたファイル名のファイルを削除するなど
    • informalな情報はメソッドのコメントで説明されることが多い
    • Method Bを呼ぶ前にMethod Aを呼ぶ必要がある、みたいなものもinformalな情報である。

4.3 Abstraction

  • Abstractionとは、実体から重要ではない詳細を除外したもの
  • 間違った抽象化には以下の2つがある
    • 抽象化したが、重要ではないものを含んでしまう
      • 結果的に他の開発者の認知負荷をあげてしまう
    • 抽象化して重要なものを隠してしまう(false abstraction)
      • そのモジュールがシンプルなものだと誤解させてしまう
  • 良い抽象化の例として、ファイルシステムが挙げられる
    • ファイルにデータを書き込む時に、ユーザーはそのデータがストレージデバイスのどのブロックに書き込まれるかなどは意識しない
    • これはファイルシステムがうまく抽象化されていて、ユーザーにとっての不必要な情報を除外しているから
    • 一方で、書き込んだデータのフラッシュについての情報は除外されていない
      • 例えばデータベースのようなソフトウェアだと、「システムがクラッシュしてもファイルに必ず書き込まれているか」を保証するために、「実際のストレージにいつデータが書き込まれるか(=フラッシュされるか)」は知っておく必要がある。そのためこの情報は抽象化されても利用者からわかるようになっている。

4.4 Deep Modules

  • モジュールの深さはコストとベネフィットで考える
    • モジュールのコスト:システムの複雑性。interfaceによって表現される
    • 小さくてシンプルなインターフェースだと複雑性は少なくなる
  • 以下がコストとベネフィットの説明の図
  • UnixのFile I/O はdeep interfaceの良い例
    • 以下の図のようにinterfaceはシンプルだが、実装は以下のような複雑である
      • ディスク上での効率的なアクセスのためのファイルの実装
      • ディレクトリ構造とその階層構造
      • ファイルのパーミッション
      • interruption, background code、およびこれらのやりとり
      • 同時アクセスが発生した場合のスケジューリング
      • アクセスしたファイルのメモリ上のキャッシュ
      • セカンダリのストレージデバイス(ディスク、Flashドライブなど)を一つのファイルシステムに統合する
    • 上記のような複雑なものは利用者からは見えない一方で、何年もの時間を経て大幅に進化している
    • 別の例だと、ガベージコレクションもdeep interfaceである
      • 利用者がほとんど意識しなくていいものだが、実装はとても複雑という意味で。

4.5 Shallow modules

  • interfaceが(提供する機能と比較して)複雑であること

      private void addNullValueForAttribute(String attirubte) {
          data.put(attribute, null);
      }
    
  • 上のコードは何も抽象化していない

  • もしこのメソッドのドキュメントを適切に書いたとしたら、コードより長いドキュメントになる
  • Shallow moduleはRed Flag
    • A shallow module is one whose interface is complicated relative to the functionality it provides. Shallow modules don’t help much in the battle against complexity, because the benefit they provide (not having to learn about how they work internally) is negated by the cost of learning and using their interfaces. Small modules tend to be shallow.
      • Shallow moduleは提供するベネフィットをラーニングコストで打ち消してしまっている
        • 小さいモジュールがshallow moduleになりやすい
  • (個人的に思ったこと)Clean Architectureって割とshallow moduleになりやすいのでは?と思った。例えばinfrastructure層とか、クライアントライブラリのメソッド呼ぶだけみたいなのが多い
    • クライアントライブラリ自体をinfrastructure層に見立てるというやり方もあるっぽい

4.6 Classitis

  • 昨今だとクラスは小さくする方が好まれている。メソッドも同様で「N行超えたら分割するべき」みたいな風潮がある
  • しかし、Deep Classを目指すのであれば、クラスは大きくなる傾向にある
  • Classitis
    • The extreme of the “classes should be small” approach is a syndrome
  • Classitisはたくさんのクラスを生み出し、それぞれのクラスはシンプルになるが全体のシステムとして見ると複雑性が高まる
    • 小さいクラスはそれ単体では十分な機能を提供できない

4.7 Examples: Java and Unix I/O

  • Classitisの典型的な例としてJavaのIOまわりのクラスの話がある。例えばJavaでオブジェクトをファイルから読むときのコードは以下のようになっている。
FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BuffferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjetInputStream(bufferedStream);
....
  • バッファリングは基本的にみんな使うので、上記のように専用のクラスを使わないと有効にならないデザインよりかは、よくあるケースに対してシンプルなデザインになっていた方が良い
    • この例だとデフォルトでバッファリングを有効にして、オプションで無効にできるとか。
  • 対照的な例として、Unix Filesystemはシンプルになっている。
    • シーケンシャルIOが最も一般的に使われるので、それをデフォルトの挙動にしている
      • readはシーケンシャルアクセスで、 lseek でランダムアクセスもできるようになっている

4.8 Conclusion

  • interfaceを抽象化してシンプルなものにして、複雑な実装はinterfaceから除外することが大事

Chapter 3 - Working Code Isn’t Enough / A Philosophy of Software Design

第3章はWorking Code Isn’t Enough (Strategic vs. Tactical Programming)というタイトル。

Tactical Programming

  • 近視眼的に目の前のタスクを終わらせるためにコードを書く
  • 目の前のタスクを終わらせることが最優先になるので、これだといい設計はもたらされない
  • これが積もり積もってシステムに複雑性をもたらす
  • 開発メンバー全員がこのアプローチで開発すると一気に複雑性が高まる
  • tactical tornado
    • Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado.

Strategic Programming

  • The first step towards becoming a good software designer is to realize that working code isn’t enough.
    • working code = 動くコードの意味?
  • Your primary goal must be to produce a great design, which also happens to work. This is strategic programming.
    • 第一のゴールを素晴らしいデザインでかつきちんと動作するものにするべき
  • 良いデザインに対して投資する、という考え方を持つべき
    • 最初に思い浮かんだ設計のアイデアをそのまま実装するのではなく、少し時間をかけてよりシンプルな設計がないかを模索する時間をかけるべき
  • どのぐらい良い設計のために時間をさくべきか?
    • 作業の10-20%とこの本ではいっている
    • 良いデザインにそのぐらいコストをかけると、数ヶ月後にはそのベネフィットを感じることができる
  • スタートアップと良い設計への投資
    • スタートアップで10-20%コストをかけることは現実的ではないと考えられているため、大抵のスタートアップではTacticalなアプローチが採用される
    • Facebook is an example of a startup that encouraged tactical programming
    • the company’s motto was “Move fast and break things.”
    • Over time the company realized that its culture was unsustainable. Eventually, Facebook changed its motto to “Move fast with solid infrastructure”

Chapter 2 - The Nature of Complexity / A Philosophy of Software Design

第2章は"The Nature of Complexity"というタイトルで、ソフトウェアのComplexityつまり複雑性についてじっくり説明されている。

Complexityの定義

Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.

Complexityの3つの症状

1. Change amplification

- とある機能追加・変更をしたいだけなのに、いろんなところを変更しないといけない。以下の図で言うと、背景色を変更したいだけなのに`bg = "red"`の定義が複数箇所あるため、これらを全て修正しないといけない、みたいな。

2. Cognitive load

  • 認知負荷。開発者が機能追加・変更のタスクを完了させるためにどのぐらいシステムの内部を知っている必要があるか、と言うこと。多ければ多いほど把握できなくなり、修正漏れなどのバグが発生することになる。
  • システム設計者は時々コードの行数で複雑性を表そうとするが、1行に処理が集約されてすぎていて逆に認知負荷を高めるケースもある。1行より複数行のコードの方がシンプルになっている場合もある

3. Unknown unknowns

  • 開発者が機能追加・変更のタスクを完了させるために、どの部分を変更すればいいのかが明らかではないこと。もしくはどの部分を変更すればいいのかの情報が明らかではないこと
  • 上の図だと、「背景色を変更したい」と言うタスクがある時に、bannerBgを変更しても全てのページの背景色が変わらないので、どの部分を変更すればいいのかが分かりにくい