Magicode logo
Magicode
9 min read

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

https://cdn.apollon.ai/media/notebox/8e647b4e-63a6-481f-8dde-5a1640867834.jpeg

はじめに

後輩のプログラミング教育をしていて、リーダブルコード※を読んでもらっても、読んだだけでは定着していなかった。
コードレビューで定着を試みているが、今後も後輩が増えていくことを考えて、何か対応策を講じようと思い、試しに演習課題を作ってみることにした。
リーダブルコードの2章以降から、気になったテクニックに対して、演習課題を作っていく。
今回は、「2.6 名前のフォーマットで情報を伝える」を元に、プログラミング言語ごとにある命名規則について触れる。
なお、プログラミング言語はpythonとする。

書籍の内容

アンダースコア・ダッシュ・大文字を使って名前に情報を詰め込むこともできる。
これはクラス名はキャメルケースにする、変数名はスネークケースなどのルールを作ることで、その名前がどの種類の情報なのかを示している。
例えば、c++で定数のプレフィクスに k を使うパターンがある。これを使うと定数とマクロ定義を区別できる。
static const int kMaxWindowHeight = 500
#define MAX_WINDOW_HEIGHT 500
他にもクラスのメンバ変数を示すために、サフィックスにアンダースコア(_)をつけたり、プレフィクスにm_をつけるパターンもある。
こうすることで、メンバ変数にアクセスしているのか、ローカル変数にアクセスしているのかがぱっと見でわかる。
class Person
{
private:
    string name_;
public:
    Person(string name) : name_(name) {}
    
    string who_are_you()
    {
        string message = “My name is “ + name_ +.;
        // messageはローカル変数で、name_はメンバー変数とすぐわかる
        return message;
    }
}
これらはプログラミング言語ごとに主流のルールがある。さらに、現場のプロジェクト内でのルールもあるので、使っている言語と現場のルールに従うと良い。

演習課題

先述した通り、このテクニックは、使っている言語や現場のルールに依存するところがある。
今回は、pythonの標準ライブラリに組み込まれているpep8という規約を確認することで、ルールに従うとどのように読めるかを体験しよう。  
課題で簡単なコードスニペットを貼るので、下記の点について考えてみよう。
  • pep8的に間違っている書き方がどれか?
  • その書き方だとどういう不都合が発生しそうか?
  • 逆に正しく書いておくとどのような意図が伝えられそうか?

課題1

※アルゴリズム自体に意味はない。書き方に関してのみ確認する。
class person:
    def __init__(self, name: str, old: int):
        self._name: str = name
        self._old: str = old

total_count: int = 0
def count(num: int) -> int:
    total_count += num
    return total_count

person1 = person(“kj”, 30)
count1 = count(1)

課題2

※アルゴリズム自体に意味はない。書き方に関してのみ確認する。
class Point:
    pass
    # 本当はちゃんと実装

def ComputeDistance(point0: Point, point1: Point) -> float:
    # … 計算
    return distance

point0: Point = Point()
Point1: Point = Point()

Distance = ComputeShortestDistance(point0, Point1)

課題3

※アルゴリズム自体に意味はない。書き方に関してのみ確認する。
class Widget:
    def __init__(self, name: str, x: int, y: int, width: int, height: int):
        self.id: UUID = uuid4()

        self.name: str = name
        self.x: int = x
        self.y: int = y
        self.width: int = width
        self.height: int = height

    def _calc_area(self) -> float:
        return width * height

widget: Widget = Widget(“Sample Dialogue”, 0, 0, 100, 50)
area: float = widget._calc_area()
widget.id = uuid4()

課題4

※アルゴリズム自体に意味はない。書き方に関してのみ確認する。
MAX_POSITION_X: int = 1000
maxpositiony: int = 1000
MINPositionX: int = 0
min_position_y: int = 0

class Widget:
    def __init__(self, name: str, x: int, y: int, width: int, height: int):
        self.id: UUID = uuid4()

        self.name: str = name
        self.x: int = min(max(x, MINPositionX), MAX_POSITION_X)
        self.y: int = min(max(y, min_position_y), maxpositiony)
        self.width: int = width
        self.height: int = height

    def _calc_area(self) -> float:
        return width * height

解答例

課題1

クラスの名前には通常 CapWords 方式を使うべきです。 https://pep8-ja.readthedocs.io/ja/latest/#section-26
personとあり、この規則に即していない。
class宣言時は、すぐ近くにclassが書いてあるからいいが、そのクラスを使うプログラマーは、ソースコードを読むか、IDEの機能で判断しなけらばならない。しかし、ルールに則ってCapWords(キャメルケース)で書いていれば、名前からクラスであることがわかる。
# クラスの名前はCapWords(=キャメルケース)を使う。
#class person:
class Person:
    def __init__(self, name: str, old: int):
        self._name:str = name
        self._old:int = old

total_count: int = 0
def count(num: int) -> int:
    total_count += num
    return total_count

# クラスの名前はCapWords(=キャメルケース)を使う。
# 元の場合、関数と見分けがつかない。
# person1: Person = person(“kj”, 30)
person1: Person = Person(“kj”, 30)
count1: int = count(1)

課題2

関数の名前は小文字のみにすべきです。また、読みやすくするために、必要に応じて単語をアンダースコアで区切るべきです。
変数の名前についても、関数と同じ規約に従います。
関数名や変数名に大文字があったりし、この規約に則していない。
課題1にあったように、関数なのかクラスなのかわからなくなる。また、この課題では、規約に則っている変数と則っていない変数がごちゃ混ぜになっていて、統一感がなく読みにくい。また、何か意図があるのではと勘ぐってしまい、余計な時間がかかる。
class Point:
    pass
    # 本当はちゃんと実装

# 関数の名前は小文字のみにする。
# 読みやすくするために必要に応じてアンダースコアで区切る。
# 要するにスネークケース
# def ComputeDistance(point0: Point, point1: Point) -> float:
def compute_distance(point0: Point, point1: Point) -> float:
    # … 計算
    return distance

point0: Point = Point()
# 変数名もスネークケースで
# Point1: Point  = Point()
point1:Point  = Point()

# ルール守ってたり守ってなかったりバラバラだと統一感がなくて読みにくい
# ルールを守っていないと、何か意図があるのかと勘ぐってしまう
# Distance = ComputeShortestDistance(point0, Point1)
distance: float = compute_distance(point0, point1)

課題3

関数の命名規約を使ってください。つまり、名前は小文字のみにして、読みやすくするために必要に応じて単語をアンダースコアで区切ります。
公開されていないメソッドやインスタンス変数にだけ、アンダースコアを先頭に付けてください。
サブクラスと名前が衝突した場合は、Python のマングリング機構を呼び出すためにアンダースコアを先頭に二つ付けてください。
メソッド名やインスタンス変数名は問題なさそう。
しかし、公開に関する規約に則していない。面積を計算する_calc_area()は外部から呼びたいメソッドのはずだが、アンダースコアが先頭に来ている。また、idにアンダースコアがついておらず、公開していると判断できるため、外部から値を書き換えても良いように見えてしまうが、idは簡単に変更していい情報ではない。
また、今回は使っていないが、サブクラスからインスタンス変数を見えなくする(いわゆるprivate)にはアンダースコアを二つつけるとできる。ただ、これは完全にprivateになったわけじゃなくて、マングリングにより、クラス名が先頭についているだけで、アクセスはできます。
class Widget:
    def __init__(self, name: str, x: int, y: int, width: int, height: int):
        # idは非公開情報
        # self.id: UUID = uuid4()
        self._id: UUID = uuid4()

        self.name: str = name
        self.x: int = x
        self.y: int = y
        self.width: int = width
        self.height: int = height

    # 公開メソッド
    # def _calc_area(self) -> float:
    def calc_area(self) -> float:
        return width * height

    # idを読み込みのみ可にする
    @property
    def id(self) -> UUID:
        return self._id 

widget: Widget = Widget(“Sample Dialogue”, 0, 0, 100, 50)
area: float = widget.calc_area()
#idに書き込もうとするとerror
#widget.id = uuid4()

課題4

定数は通常モジュールレベルで定義します。全ての定数は大文字で書き、単語をアンダースコアで区切ります。例として MAX_OVERFLOW や TOTAL があります。
positionの範囲を指定する定数を設けているが、定数の書き方に則していない。そもそも区切りがなく読みにくいものや、変数名のルールに従っていて、書き換えてもよさそうに見えるものがある。(python は言語仕様上、定数にできない)
MAX_POSITION_X: int = 1000
MAX_POSITION_Y: int = 1000
MIN_POSITION_X: int = 0
MIN_POSITION_Y: int = 0

# 区切りがなくて読みにくい
# maxpositiony: int = 1000
# 書き方にルールがなくて読みにくい
# MINPositionX: int = 0
# スネークケースは変数名のルールなので、変数に見えてしまう
# min_position_y: int = 0

class Widget:
    def __init__(self, name: str, x: int, y: int, width: int, height: int):
        self.id: UUID = uuid.uuid4()

        self.name: str = name
        self.x: int = min(max(x, MINPositionX), MAX_POSITION_X)
        self.y: int = min(max(y, min_position_y), maxpositiony)
        self.width: int = width
        self.height: int = height

    def _calc_area(self) -> float:
        return width * height

終わりに

リーダブルコード「2.6 名前のフォーマットで情報を伝える」を元に、プログラミング言語ごとにある命名規則について触れてみた。
課題に関しては、やってみるとなかなか難しかった。間違っているとか、こう書いた方がいいのでは?などあったら是非コメントして欲しい。

Discussion

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