Upgrade to Pro — share decks privately, control downloads, hide ads and more …

エンジニア達の_完全に理解した_-コードでの契約ってなんだ_.pdf

ツノ
June 25, 2024
120

 エンジニア達の_完全に理解した_-コードでの契約ってなんだ_.pdf

ツノ

June 25, 2024
Tweet

Transcript

  1. コード品質の柱 高品質なコードを書くための戦略として挙げられている6つの柱 1. 📖 : コードを読みやすくする 2. ❗️ : 想定外の事態をなくす

    3. 👌 : 誤用しにくいコードを書く 4. 🤖 : コードをモジュール化 5. ♻️ : コードを再利用、汎用化しやすくする 6. ✏️ : テストしやすいコードを書く
  2. 柱の詳細(今回は2つのみ) ❗️ : 想定外の事態をなくす あるタスクをこなそうとした時に、想定外が起きることは悪いこと 例:レストランの注文で「マルゲリータ 🍕」と「マルガリータ 🍸」の注文は間違えやすい 👌 :

    誤用しにくいコードを書く これをしないと作成者が想定していない利用方法で使われる 例:テレビの電源ケーブル 📺とHDMIケーブル 🖥は形が違うので誤用しようがない この2の柱を実現するために「コードでの契約」が必要!
  3. コードの契約:契約の中の細則 コードでの契約というメンタルモデルを頭に入れると、 「契約の中の細則」もコードの世界に落とし込める # 例:レンタサイクルのアプリ - 明確な契約 - レンタサイクルを借りられる -

    レンタル費用は150円/時 - 契約の中の細則 - 時速30kmを超えた場合は罰金5000円が発生する※ ※利用者が意識して監視する必要がある  (速度を抑制する機能がついていない) # 例:実際のコード - 明確な契約 - 関数とクラスの名前 - パラメーターの型 - 戻り値の型 - 契約の中の細則 - コメント - ドキュメント 言いたいこと 「契約の中の細則」は落とし穴のようなものになりがち → できる限りコードでの契約に寄せるべき
  4. マジックバリュー(固定値)を戻り値に使わない 例:ユーザーに関する情報を保存するクラスがあった場合 🙅 BadCode 🙆 GoodCode コードの作成者とレビュアーはこの動作を確認してい るはずだが 新規開発者が平均年齢を出す処理を getAge()

    を使っ て実装を行う場合バグる 呼び出し元でnullが来ることが検知でき、 新規開発者が落とし穴があることが把握できる class User { private age = null getAge():number{ if(this.age === null){ return -1 // 入力がない場合は-1を返す } return this.age } } class User { private age = null getAge():number | null{ if(this.age === null){ return null } return this.age } }
  5. 呼び出し元を意識したnullオブジェクト利用を心がける 例:HTMLの class を見て強調しているか確認しているコード 🙅 BadCode 🙆 GoodCode getClassNames でnullが帰ってくる

    →呼び出しもとでnullチェックを 呼び出し元のコードがスッキリした function getClassNames(element: HTMLElement): Set<string> | const attribute = element.getAttribute("class"); if (attribute === null) { return null; } return new Set(attribute.split(" ")); } function isElementHighlighted(element: HTMLElement): boolea const classNames = getClassNames(element); if (classNames === null) { // 使用まえにnullチェック return false; } return classNames.has("highlighted"); } function getClassNames(element: HTMLElement): Set<string> { const attribute = element.getAttribute("class"); if (attribute === null) { return new Set(); } return new Set(attribute.split(" ")); } function isElementHighlighted(element: HTMLElement): boolea // null値チェックの必要がない return getClassNames(element).has("highlighted"); }
  6. 将来的にも有効に使えるように列挙型処理を利用する 例:列挙型の条件分岐(下のコードに新しい列挙 Top が追加された場合) 🙅 BadCode 🙆 GoodCode コンパイルエラーを検知できない コンパイルエラーを検知できる

    実装時にエラーに気づくことができる const Position = { Right: 0, Left: 1, } as const; type Position = (typeof Position)[keyof typeof Position]; // 上は type Position = 0 | 1 と同じ意味になります function toJapanese(position: Position) { if (position === Position.Right) { return "右"; } return "左"; } const Position = { Right: 0, Left: 1, } as const; type Position = (typeof Position)[keyof typeof Position]; // 上は type Position = 0 | 1 と同じ意味になります function toJapanese(position: Position) { switch (position) { case Position.Right: return "右"; case Position.Left: return "左"; } }
  7. 全てを不変にする(ことを検討する) 例:フォント情報を保存するクラス 🙅 BadCode 🙆 GoodCode fontFamily が呼び出し元で書き換えられる fontFamily が

    ReadonlyArray なので不変 安心して使えるね class TextOptions { constructor( private fontFamily: Font[], private fontSize: number ){} getFontFamily(): Font[] { return this.fontFamily; } getFontSize(): number { return this.fontSize; } } class TextOptions { constructor( fontFamily: ReadonlyArray<Font>, private fontSize: number ) {} getFontFamily(): ReadonlyArray<Font> { return this.fontFamily; } getFontSize(): number { return this.fontSize; } }
  8. 汎用的なデータ型は避ける 例:地図上に指定位置をマークするメソッド 🙅 BadCode 🙆 GoodCode class LocationDisplay { private

    readonly map: DrawableMap; /** * 与えられたすべての座標の位置を地図上にマークする * * リストのリストを受け入れる。内部のリストには正確に2つの値が含ま * 1番目の値は緯度、2番目の値は経度 (両方とも度) */ markLocationsOnMap(locations: Array<[number, number]>): for (const location of locations) { this.map.markLocation(location[0], location[1]) } } } /** 緯度と経度を度数で表す */ class LatLong { constructor( private readonly latitude: number, private readonly longitude: number) {} getLatitude = () => this.latitude; getLongitude =() => this.longitude; } } class LocationDisplay { private readonly map: DrawableMap; markLocationsOnMap(locations: LatLong[]): void { for (const location of locations) { this.map.markLocation(location.getLatitude,loca } } } 契約の中の細則(コメント)でカバーしようとしている 何を表しているかが明確なため誤用を避けられる
  9. まとめ 高品質なコード(GoodCode)を書くには 6つの柱を満たす必要がある 今回は「 ❗️ : 想定外の事態をなくす」 、 「 👌

    : 誤用しにくいコードを書く」を特集した コードでの契約 上記2つを達成する戦略として、コード上で縛りを入れる 具体例 マジックバリュー(固定値)を戻り値に使わない 呼び出し元を意識したnullオブジェクト利用を心がける 将来的にも有効に使えるように列挙型処理を利用する 全てを不変にする(ことを検討する) 汎用的なデータ型は避ける