はじめてのIT勉強会 #2 Readable Code Part 2

はじめてのIT勉強会 #2 Readable Code Part 2

仙台で開催した「はじめてのIT勉強会」第二回のセッションスライドです。
リーダブルコードの紹介と入門的な解説内容になっています。

4917aec6bbd9147620f8392ea804fad6?s=128

naoki

May 25, 2017
Tweet

Transcript

  1. Readable Code Part 2: 誤解されない名前 Study session in SENDAI

  2. 私について

  3. 誤解されない名前?

  4. results := datastore.Query().Filter("year <= 2011") このコードの実行結果は?

  5. results := datastore.Query().Filter("year <= 2011") このコードの実行結果は? A: 2011年以前のデータを取得する B: 2011年以前のデータを除いて取得する

  6. 「どっちの意味だっけ?」 と思ったら、 それは誤解されるコード

  7. POINT 自問自答して積極的に「誤解」を探し ていけば、曖昧な名前を見つけること が出来る。

  8. 1.間違ってないけど曖昧な名前

  9. 修正前のコード results := datastore.Query().Filter("year <= 2011")

  10. 修正前のコード results := datastore.Query().Filter("year <= 2011") A: 2011年以前のデータを取得する results :=

    datastore.Query().Select("year <= 2011")
  11. 修正前のコード results := datastore.Query().Filter("year <= 2011") A: 2011年以前のデータを取得する results :=

    datastore.Query().Select("year <= 2011") B: 2011年以前のデータを除いて取得する results := datastore.Query().Exclude("year <= 2011")
  12. // textの末尾を切り落として、「…」をつける func clip(text string, length int) string

  13. // textの末尾を切り落として、「…」をつける func clip(text string, length int) string clipped :=

    clip("Hello, readable code!", 6)
  14. // textの末尾を切り落として、「…」をつける func clip(text string, length int) string // 後ろの6文字を切り落とし?

    (remove:取り除く) assert.Equal(t, "Hello, readable...", clipped) clipped := clip("Hello, readable code!", 6)
  15. // textの末尾を切り落として、「…」をつける func clip(text string, length int) string // 後ろの6文字を切り落とし?

    (remove:取り除く) assert.Equal(t, "Hello, readable...", clipped) clipped := clip("Hello, readable code!", 6) // 6文字までで切り落とし? (truncate:切り詰める) assert.Equal(t, "Hello,...", clipped)
  16. // textの末尾を切り落として、「…」をつける func clip(text string, length int) string なんの長さ? バイト数

    maxBytes 文字数 maxChars 単語数 maxWords
  17. // textの末尾を切り落として、「…」をつける func clip(text string, length int) string なんの長さ? バイト数

    maxBytes 文字数 maxChars 単語数 maxWords // 修正例 func truncate(text string, maxChars int) string
  18. 2.誤解される限界値

  19. const CART_TOO_BIG_LIMIT int = 10 // ショッピングカート内のアイテムが10件より多い場合はエラー if len(shoppingCart) >=

    CART_TOO_BIG_LIMIT { return errors.New("カートにある商品が多すぎます。") }
  20. const CART_TOO_BIG_LIMIT int = 10 // ショッピングカート内のアイテムが10件より多い場合はエラー if len(shoppingCart) >=

    CART_TOO_BIG_LIMIT { return errors.New("カートにある商品が多すぎます。") } このコードにはバグがあります。
  21. const CART_TOO_BIG_LIMIT int = 10 // ショッピングカート内のアイテムが10件より多い場合はエラー if len(shoppingCart) >=

    CART_TOO_BIG_LIMIT { return errors.New("カートにある商品が多すぎます。") } このコードにはバグがあります。 Ans:ショッピングカートに9件しか入らない。
  22. LIMITでは 「未満(限界値を含まない)」なのか 「以下(限界値を含む)」なのかが 曖昧になってしまう。

  23. //修正版 const MAX_ITEMS_IN_CART int = 10 // ショッピングカート内のアイテムが10件より多い場合はエラー if len(shoppingCart)

    > MAX_ITEMS_IN_CART { return errors.New("カートにある商品が多すぎます。") }
  24. POINT 限界値を明確にするためには、 MINやMAXを使用する。

  25. 3.範囲の指定

  26. // 指定した範囲の数値を取得する func intRange(start int, stop int) []int iRange :=

    intRange(2, 4)
  27. // 指定した範囲の数値を取得する func intRange(start int, stop int) []int iRange :=

    intRange(2, 4) iRangeの値は何が入る?
  28. // 指定した範囲の数値を取得する func intRange(start int, stop int) []int iRange :=

    intRange(2, 4) iRangeの値は何が入る? [2, 3]? [2, 3, 4]?
  29. Startは明確だけど、Stopは 「未満(限界値を含まない)」なのか 「以下(限界値を含む)」なのかが 曖昧になってしまう。

  30. 最後の要素まで含む範囲なら firstとlastを使う ↓first ↓last 1 2 3 4 5

  31. // 修正版 func intRange(first int, last int) []int iRange :=

    intRange(2, 4) // [2, 3, 4] maxとminを使用しても良い
  32. 最後の要素まで含まない範囲なら beginとendを使う 1 2 3 4 5 ↓begin ↓end //

    指定した範囲の数値を取得する func intRange(begin int, end int) []int iRange := intRange(2, 4) // [2, 3]
  33. 4.ブール値の名前

  34. readPassword := true どんな意味かわからないブール値 パスワードを 「読み込む必要がある」のか、 「もう読み込んだ」のか...etc

  35. 「読み込む必要がある 」 「 も う 読 み 込 ん だ

    」 :needPassword :isAuthenticated
  36. disableSsl := false ブール値の否定形は避ける

  37. disableSsl := false ブール値の否定形は避ける enableSsl := true

  38. POINT ブール値は頭にis, has, can, should などをつけて、分かりやすく 否定形は避ける

  39. 5.ユーザーの期待に合わせる

  40. この処理のコストは? mean := staticsController.getMean() getterなので一般的にO(1)と考える...

  41. // 実装 type StaticsCollector struct{} func (s *StaticsCollector) addSample(x float64)

    {} func (s *StaticsCollector) getMean() float64 { // 全てのサンプルをイテレートして、total / num_samples を返す。... } 実装はO(n)だった... もしサンプルが1000万件あったらorz
  42. get*()からcompute等へ変更する // 実装 type StaticsCollector struct{} func (s *StaticsCollector) addSample(x

    float64) {} func (s *StaticsCollector) computeMean() float64 { // 全てのサンプルをイテレートして、total / num_samples を返す。... } あるいは処理を工夫してO(1)になるようにする
  43. POINT 読み手の期待に反しない 名前をつける

  44. 6.複数の名前を検討する

  45. exprimentId = 100 description = "フォントを14ptに上げる" fontSize = 14pt ...

    // 複数の設定項目 trafficFraction = 5% ある実験をするための設定ファイル
  46. exprimentId = 101 description = "フォントを13ptに上げる" fontSize = 13pt ...

    // 複数の設定項目 [再利用する実験ID] = 100 同じような実験をする必要があるが、 設定ファイルをコピペして作るのは嫌なので... こんなふうに書けるようにしたい
  47. experimentId = 100 template = 100 名前を考えてみよう 1.template テンプレートは抽象的な穴あきの項目に具体的な 値を埋め込んで使用するもの。

    具体的な実験IDを再利用しているので、誤解を招く。
  48. experimentId = 100 reuse = 100 名前を考えてみよう 2.reuse reuseだけだと「100回再利用可能」と誤解される可 能性がある。

    reuseIdにすれば多少よくなるけど、「再利用ID」とい う別のなにかに勘違いされるかもしれない。
  49. experimentId = 100 copy = 100 名前を考えてみよう 3.copy copyだけだと「この実験を100回コピーする」のかと 勘違いされてしまうかも。

    copyExperimentIdとすると何をコピーしているかが すぐに理解できる。
  50. experimentId = 100 inherit = 100 名前を考えてみよう 4.inherit 継承という言葉はプログラマになじみがあるので理 解しやすい。

    inheritFromExprimentIdとすれば継承元が何かすぐ にわかる。
  51. この章のまとめ

  52. 誤解されない名前をつけるには ・名前が他の意味に捉えられないか自問自答する *filterよりselect、limitよりmaxなど、より意図が明確な名前 ・限界値や範囲は慣習に従う *限界値はmin/max、範囲はfirst/lastやbegin/endなど使い分ける ・ブール値の名前に気をつける *それがブール値だと分かるようにis, hasなどを使う *disableSslのような否定形を極力使わない ・読み手の期待を裏切らない

    *get*()やsize()のような処理が軽く見える名前で重い処理をしない ・複数の名前を検討する *いくつか候補を挙げ、ベストな名前を選ぶ
  53. ご清聴、ありがとうございました