JavaScriptを有効にしてください

【プログラマー脳】質の良いトグルを脳からFetchできる人は熟練者

 ·  ☕ 9 分で読めます

熟練者と初心者を分けるのは、長期記憶に保存されているパターンの量だった。そして、いいコードにはパターンがある。

熟練者が読みづらいと言っているコードは、パターンから逸脱しているコードのことだった。将棋の例えになって恐縮だが、左美濃囲い右四間飛車と言っているのに、実際には駒組みがあべこべになっていて、狙いや目的がまったく伝わってこないようなものだ。たとえば通常なら「先に駒をこう動かしてから、こう囲い始める」のが一般的なセオリーなのに、そこを大きく外れているので、「何をしたいのか分からない」「次の手が読めない」と熟練者ほど混乱してしまう。
プログラムでも同じで、本来の設計パターンに沿って書かれるはずのところが崩れていると、ほかの開発者から見ると「どんな意図でこのコードを書いたのか」が把握しづらくなる。結果として、理解や改修に時間がかかり、バグの温床にもなりやすいわけだ。結局は「読みやすいコード=再利用しやすいコード」という土台を守ることが大事で、それを踏み外してしまうと熟練者ほど違和感を覚えやすい、ということになる。

保存されているパターンをサッと取り出せるように工夫して、ワーキングメモリの負荷を減らす。脳科学的に、ワーキングメモリに載せておけるチャンクの数は2〜6、多くても7個程度なので、チャンクの一つあたりをどれだけ大きくできるかで、熟練者になれるかどうかが決まると思った。

チャンクは拡大と縮小ができる。『脳に収まるコード』に出ていたGoogleマップの拡大縮小と概念的には似ている。日本列島から渋谷フクラスまでを拡大する流れは、importしてきたパッケージのprivateメソッドをコードジャンプで覗いていく流れにそっくりだ。マークダウン形式で書かれたトグルを開いたり閉じたりする動作をチャンクのメンタルモデルとして採用してもいい。私のチャンクに対する心像は、トグルだ。

そのため、トグルにつける名前は、名前を見たら中身を思い出せるようなものにするのがベスト。熟練者は命名が上手い。というか、命名をうまくしないと覚えないといけない詳細が増えるため、ワーキングメモリの容量が足りなくなる。ワーキングメモリの容量が足りないと人間の脳はどうなるのか。オーバーフローが発生したCPUを想像してみて欲しい。あんな感じだ。いわゆる認知負荷が高まって、「無理!」ってなる。

この「無理!」っていう状況を引き起こすコードが「読み辛いコード」と巷ではよくいわれている。「プログラマー脳」によると、プログラマーの仕事の約7割がコードを「読む」時間に充てられているそう。読みづらいコードは、読みたくない。

面白いのが、熟練者も初心者も同じ人間であり、脳科学的には、ワーキングメモリに載せるチャンクの量はほぼ同じということだ。つまり、量ではなく質が2者を分けているということだと思う。質とは、処理対象とワーキングメモリになっているトグル名がどれだけ一致しているか、トグル名はトグルの中身をどれだけ説明できているのか、だと私は考える。

読みづらいコードと読みやすいコード

ここで、読みづらいコードと、トグル(チャンク)を意識して読みやすくなったコードを見てみることにする。たとえば、以下のようなPythonコードを考えてみよう。

読みづらいコード例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def calc(d):
    r = []
    for i in range(len(d)):
        f = 0
        for j in range(len(d) - 1):
            if d[j] > d[j+1]:
                t = d[j+1]
                d[j+1] = d[j]
                d[j] = t
                f = 1
        if f == 0:
            break
    return d
  • 変数名がdやf、tなどで、一見何の処理をしているのか分かりづらい。
  • 処理の意図が見えないため、読み手はコードの一行一行にいちいち脳内で展開をかけなければいけない。

この「いちいち展開する」部分がどんどんワーキングメモリを埋めて、「無理!」 となりやすい。

読みやすいコード例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def bubble_sort(numbers):
    """
    与えられたリストをバブルソートで昇順に並び替えて返す
    """
    for i in range(len(numbers)):
        swapped = False
        for j in range(len(numbers) - 1):
            if numbers[j] > numbers[j + 1]:
                # 要素を入れ替える
                numbers[j], numbers[j + 1] = numbers[j + 1], numbers[j]
                swapped = True
        # 途中で入れ替えがなければ、既にソート済み
        if not swapped:
            break
    return numbers
  • bubble_sort という名前を見れば、「これはバブルソートだ」とトグルの名前を聞いただけで処理内容を想起しやすい。
  • numbers, swapped などの変数名から機能や役割がわかりやすい。

コード全体を**「バブルソート」というトグル**として認識できるので、必要に応じて中身をパッと開いて確認し、またすぐ閉じることができる(=拡大縮小可能なチャンク)。

こうして、質の高いトグルを長期記憶に保存できるかどうかで熟練者になれるかどうかが決まる、というのが私の仮説だ。

質の高いトグルをどう作るか

とすると、「質の高いトグルをどう作るか」という疑問が浮かぶだろう。結局、勉強しかない。ただ、今までの勉強に対するメンタルモデルと、『プログラマー脳』を読んだ後の勉強に対するそれは異なっている実感がある。何かを勉強するに際して、視座が変わったのだ。

「アウトプットありきの勉強がインプットの質を決める」と呼ばれるあれだ。「質の高いトグルを作る」というアウトプットが決まっている。常に目的を持った状態で勉強に臨めるため、インプットの質は今までに比べたら格段に高くなるだろう。

話を元に戻すが、「質の高いトグルをどう作るか」。トグルの質は、

  1. これ以上開いても言葉の意味は変わらないという課題内在性と関連負荷のみが書かれていること
  2. その量の多寡がちょうどいいこと

こういうトグルを私は「質の高いトグル」と考えている。自分の得意なコースに持って行ってシュートしたり、自分の得意な公式や四則演算にまで分解してから問題を解いたり。これ以上展開できない因果は変わらないという地点まで知識が詰め込まれていて、かつ、ピンチイン(日本列島→渋谷フクラス)がしやすいトグルがベストだ。

要は、「副作用がなくて取り出しやすい関数」 が質の高いトグルだ。ピンチイン(日本列島→渋谷フクラス)とピンチアウト(渋谷フクラス→日本列島)のどちらのアプローチがいいのか、私は両方をいい感じに活用する、という結論に至った。臨機応変、状況に応じて、的な。すみません。

パターンを知っている人の脳内

GoFのデザインパターンやAWSのAWS Well-Architected フレームワークなんかは、トグルが格納される箱のいい例だ。そう、トグルはカテゴライズされて棚にしまわれている。何らかの処理をするときに棚から、処理対象のトグルを取り出す(Fetch)。

コーディングでいうと、

「この書き方は典型的な二分探索のパターンだから処理は深く追わないでよさそうだな」

と、二分探索を知っている人はなるが、知らない人は、

「配列を真ん中で分割しているが、これは正しいのか? どこで要素が判定されるのか?」

など、一行ずつ深く追う必要が出てくる。このとき、

パターンを知っている人のワーキングメモリはこう

1
二分探索

知らない人のワーキングメモリは

1
真ん中で分割マージリターン...

知らない人は、これだけでワーキングメモリ容量3倍逼迫している。パターンを知っていると、ワーキングメモリ容量を節約できる。つまりコードリーディングが楽になるとわかった。

Blind75の復習を行なっていく

そんなわけで、私の次のステップはBlind75の復習だ。

基本的にPythonで書いていく。Blind75がPythonで解説されているのもそうだが、型なしの言語はありに比べて記述が少なくて済むので、よりロジックに集中しやすいというのが本意だ。

方針は、質のいいトグルを作ること。ボトムアップとトップダウンを組み合わせよう。ざっくりカテゴリを分けて、コードを書いていく。その後、各アルゴリズムのコードをカテゴリ分けしていく。「トグル名を見て、解法を思いつく」ことができるようなトグルを使っていく。

まずはカテゴリを分けよう。Blind75のカテゴリをそのまま転用する。

https://leetcode.com/discuss/general-discussion/460599/blind-75-leetcode-questions

14パターン

https://hackernoon.com/14-patterns-to-ace-any-coding-interview-question-c5bb3357f6ed

1. Sliding Window

2. Two Pointers or Iterators

3. Fast and Slow pointers

4. Merge Intervals

5. Cyclic sort

6. In-place reversal of linked list

7. Tree BFS

8. Tree DFS

9. Two heaps

10. Subsets

11. Modified binary search

12. Top K elements

13. K-way Merge

14. Topological sort

たとえば、「Modified binary search」のトグルは「mid計算→条件分岐→探索範囲の更新」というバリエーションをまとめたもの。パターンとして覚えてしまえば、実装時にワーキングメモリの使用をぐっと抑えられる。以下に軽い例を示す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def search_in_rotated_array(nums, target):
    """
    ローテーションがかかったソート済み配列からtargetのインデックスを探す(なければ-1)。
    Modified Binary Searchの典型パターン。
    """
    left, right = 0, len(nums) - 1
    
    while left <= right:
        mid = (left + right) // 2
        # midがtargetそのものなら返す
        if nums[mid] == target:
            return mid
        
        # 左半分がソート済みの場合
        if nums[left] <= nums[mid]:
            # targetが左区間に入るかどうか
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        # 右半分がソート済み
        else:
            # targetが右区間に入るかどうか
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1

    return -1

これを 「Modified Binary Search」 というトグルとして覚えておくだけで、ソースコードを読むときに「この書き方は典型的なやつだな」と、ワーキングメモリを節約できるようになる。

まとめ

熟練者と初心者の差は、長期記憶に保存されているパターン(=チャンク、トグル)の量や質にある。

質の高いトグルを長期記憶に保存すると、コードリーディングが圧倒的に楽になる。

「質の高いトグルをどう作るか」を常に考えつつ勉強すると、アウトプット前提でインプットがより濃密になる。

Blind75 などで定番アルゴリズムをトグル化して棚にしまっておけば、実際の現場で取り出しやすい(=Fetchしやすい)。結局のところ、**「命名をうまくして、頭の中のトグルを増やし、ワーキングメモリの負荷を減らす」**というのが、プログラマーとしてのスキルを磨くカギになる、と私は考えている。以上が「プログラマー脳」を読んで得られたインサイトと、私の学習方針だ。これを意識して、当面はBlind75などを中心にアルゴリズムやデータ構造のコードを書きながら、質の高いトグル作りを続けていきたい。


octpsubaru
著者
octpsubaru
Web Developer