Magicode logo
Magicode
5 min read

リーダブルコードの演習課題を作ってみる①

https://cdn.apollon.ai/media/notebox/blob_A98sM4b

はじめに

後輩のプログラミング教育をしていて、リーダブルコード※を読んでもらっても、読んだだけでは定着していなかった。
コードレビューで定着を試みているが、今後も後輩が増えていくことを考えて、何か対応策を講じようと思い、試しに演習課題を作ってみることにした。
リーダブルコードの2章以降から、気になったテクニックに対して、演習課題を作っていく。
今回は、「2.1 明確な単語を選ぶ」を題材にする。
なお、プログラミング言語はpythonとする。

書籍の内容

「名前に情報を詰め込む」には、明確な単語を選ばなければいけない。「空虚な」単語は避けるべきだ。
とある。 広い意味を持つ単語を使うと、解釈の幅ができてしまい、理解が難しくなってしまうからだ。
書籍では、getPage(url), BinaryTree.size(), Thread.Stop() を参考に説明していた。

getPage(url)

get は意味が広く、どのような手段で手に入れるかはわからない。別にそんなもの気にしないというケースもあるかもしれないが、手段を理解しておくべき時もある。 例えば、キャッシュを使っているか否かを利用者側が知っていれば、高速化検討で余計なことを調べなくて済む。DBやネットワーク越しのサーバーから取得していることを利用者が把握できていれば、ネットワークでのエラー処理を入れることができる。
また、getを使うのは、いわゆるgetterに限定した方が混乱を招きにくいと言われている。

BinaryTree.size()

Size() は何かの大きさであることはわかるが、BinaryTreeだと 大きさ に関する情報が多すぎて、どれを指しているのかわからない。そのため、書籍にあるように、Heightなどを使ってどのような大きさなのかを示すと分かりやすくなる。
一方で、これは書籍に記載されていることではないが、UI のライブラリだとsize表現をよく見かける。おそらく、矩形の高さ・幅をまとめたデータだなと想像できるからだろう。このように、sizeだからダメなのではなく、なんのsizeかを特定できるか?が重要な判断基準となる。

Thread.Stop()

Stop()という名前でもいいけど、動作に合わせてもっと明確な名前をつけた方がいいと思う。例えば、取り消しができない重い操作なら、Kill()にするといい。あとからResume() できるなら、Pause()にしてもいいかもしれない。
これは音楽や動画の再生ライブラリを読んでみると良いだろう。

演習課題

本テクニックは、プログラミングに限らず、資料作成でも使える。それを踏まえて課題を出す。
4問の課題を出す。 それに対して、
  • 意味が広い単語はあるか?
  • どのようなケースだと都合が悪そうか?
  • どのように直すと良さそうか?
を考えてみてほしい。

課題1

class Cart:
    def __init__(self):
        self._items: list[Item] = []

    def append(self, item: Item):
        self._items.append(item)

    def remove(self, item: Item):
        self._items.remove(item)
    
    def check(self) -> bool:
        return self._items is None or len(self._items) == 0

課題2

class Counter:
    def __init__(self):
        self._count = 0
    
    def count(self, num: int):
        self._count += num

    def total_count(self) -> int:
        return self._count

課題3

この問題ではプログラムではなく、資料について扱う。
ECサイトで商品を購入する際の資料の一部
ユーザーが商品をカートに追加
※なお、このECサイトは1企業の販売サイトではなく、販売店側も利用できるものとする

課題4

この問題でも資料について扱う。
オブジェクト一覧の中から、同じidのものを探し出す。
※ unityなどの3Dシーンを扱っているとする

解答例

課題1

check() の意味が広くて何がしたいのかわからない。
ここではNone なのか空なのかを確認しているので
追記:「Cartでcheckだから、会計かと思った」というコメントをもらった。なるほど、そういう解釈もある。今回は、そのように解釈がばらつきそうな名前は避けようというテクニックである。
class Cart:
    def __init__(self):
        self._items: list[Item] = []

    def append(self, item: Item):
        self._items.append(item)

    def remove(self, item: Item):
        self._items.remove(item)
    
    def is_none_or_empty(self) -> bool:
        return self._items is None or len(self._items) == 0

課題2

そもそもざっくりし過ぎであるが、countは動詞も名詞もあり、動詞だと「数を追加する」名詞だと「数えてきた総数を返す」と読める。 ここでは動詞の意味で使っているので、その意味に合いそうな単語に言い換えるとよさそうだ。
class Counter:
    def __init__(self):
        self._count _ 0
    
    def increment(self, num: int):
        self._count += num

    def total_count(self) -> int:
        return self._count
またそもそもCounterってなんだ?と曖昧なので、そこに対して具体的な名前を考えるもの良い。今回は、例題ということで仕様の設定が曖昧なので難しいが。

課題3

ユーザーという言葉の意味が広く、このECサイトのユーザーとしては販売店の人も考えられる。文脈によって判断はできるだろうが、いちいちその判断に脳内プロセッサを使いたくないので、消費者や購入者などの方がわかりやすい。

課題4

オブジェクトという言葉の意味が広く曖昧になっている。プログラミング用語としては、クラスを実体化したオブジェクトが考えられる。一方、unityなどの3Dシーンなどでは、3Dシーンにある物体を指すことも考えられる。
プログラミング用語の方ならインスタンスと表記することもできるし、3Dシーンの方なら特別な名前をつける(例えばunityだとGameObject)ことで曖昧さを回避することができる。

終わりに

リーダブルコード「2.1 明確な単語を選ぶ」について、軽く説明と演習課題を作ってみた。
課題に関しては、やってみるとなかなか難しかった。間違っているとか、こう書いた方がいいのでは?などあったら是非コメントして欲しい。

Discussion

コメントにはログインが必要です。