読者です 読者をやめる 読者になる 読者になる

Blank?=False

「呉下の阿蒙にあらず」をモットーにしたITエンジニアの日々

汝、SLAPを愛せよ。

tips 日記 プログラミング

SLAPという言葉があります。
Single Level of Abstraction Principleの略ですが、DRY,YAGNIと比べると認知度が低く感じます。
ぜひ知ってほしいものなので紹介します。

SLAPとは

まず、ソースコードは低水準なもの、高水準なものがあります。
この水準を揃えたコードを書きましょう、というのがSLAPの基本的考え方になります。

水準が同じ処理を1まとめにして関数化し、その関数をより上の水準の関数から呼び出すような階層構造にすることで、 全体に動きが俯瞰しやすくなります。

高水準なコードとは

オブジェクト指向言語であれば、Publicで外部公開されたメソッドがこれにあたります。 例えば、ボタンをクリックした時にコールされる関数やメソッド等になります。
ユーザー側、外部向けのコードというイメージです。

低水準なコードとは

オブジェクト指向言語であれば、Privateで非公開のメソッドがこれにあたります。 実際の処理(ロジック)、計算式、APIの利用やファイルの読み書き、インターフェース等になります。
より内部的なコードというイメージです。

大切なこと

ここで、高水準、低水準と2つを説明していますが、実際のプログラミングを2つの水準に分けることは非常に難しく、
むしろ無理に2つの水準に分けた結果読みにくくなってしまうかもしれません。
大切なのは水準を作ってそれを揃えることなので、2つだけではなく3つ、4つ、あっても構いません。
プロダクティブ・プログラマでは高水準、低水準の2つしか説明してませんが、より実用的にするのであれば、
高水準、低水準ではなくレイヤの階層構造をイメージしてください。

例えば、以下のように分けていきます。

  • ユーザーの操作を受ける最上位レイヤ
  • 最上位レイヤが持つモデルクラスの公開メソッドとなる第2レイヤ
  • モデルクラスの公開メソッド内で呼び出される実際の処理の第3レイヤ
  • 実際の処理の中で呼び出されるライブラリの関数となる第4レイヤ


繰り返しますが、目的は水準を作ってそれを揃えることで、2つの水準にわけることではありません。

類似したプログラミングの原則

実装パターンという本では「宣言型の表現」という言葉があり、これはコードを宣言的に書きましょうという原則で、
ソースコードはできるだけ条件分岐や繰り返しがない方がわかりやすくなる、ということです。

例えば、

  1. ○○のデータを△△にコピーする
  2. △△を□□を使って解析する
  3. □□を◇◇にアップロードする

というビジネスロジックがあり、それぞれをLogic1, Logc2, Logic3とします。
それを関数にする場合、以下のようにすれば読みやすくなる、ということです。

def bizlogic
  Logic1
  Logic2
  Logic3
end

個人的にですが、これとSLAPは近いものが有ると思っています。
何故かと言うと、処理の水準を揃えようとするとそれぞれの水準で別の関数になりますので、それを1つ上の水準で利用すると
自ずと宣言型の表現に近い形になると思います。
ただし、必ずしも高いの水準で宣言型の表現ができるわけではなく、
最も高い水準のボタンクリック時にコールされるイベントでも、チェックボックスがチェックされている・されていないで処理を変える事はあります。

def button_click
  if checkbox = true 
    foo
  else
    bar
  end
end

なのでできるだけ条件分岐、繰り返しを使わないというわけです。

メリット

  • 高水準で宣言的に書かれた関数が処理の要約となるため読みやすくなる
  • 処理の階層が俯瞰でき、構造が理解しやすくなる

どうすればいいの?

以下の3つを意識すればSLAPに沿ったコードが書けるようになります。

複合関数とロジック関数(オブジェクト指向ならメソッド)をはっきり分けること

複合関数とは

他の関数を呼び出すのみで自分自身は計算式などの処理を関数を集合関数と言います。
以下はbar関数とbaz関数を呼び出す複合関数fooの例です。

def foo
  bar
  baz
end

この複合関数がソースコードの要約として読めるようになります。
複合関数はできるだけ宣言型の表現で書くようにすることでよりわかりやすくなります。
この複合関数で呼び出す関数・メソッド名はわかりやすい命名にすることが重要です。
もしわかりにくい名前にしてしまうと、要約として意味をなさなくなります。
また、この書き方はComposed Methodというデザインパターンとなっています。

ロジック関数とは

実際の処理を書く関数になります。
このロジック関数は、1つだけの処理を実装し、複数の処理は実装しないようにします。

より下の水準のロジック関数を呼び出すことは構いませんが、複合関数は呼び出さないようにします。

Publicなメソッドで実装の詳細を書かないこと

Publicなメソッドは、基本的に集合関数にします。
なぜかというと、後から他の所から利用したいと思い、コードを読んだ時に処理の内容を理解しやすくするためです。

なのでPublicなメソッドはわかりやすくするため詳細なロジックは書かないようにしましょう。
詳細なロジックは隠蔽されたprivateメソッドで書くようにするべきです。

階層構造を考え、ロジック関数から複合関数を呼び出す処理は行わないこと

ロジック関数は、関数の階層構造の末端と考えます。
もし、ロジック関数から複合関数を呼び出すような処理が必要であれば、
そのロジック関数を呼び出す複合関数でその複合関数を呼び出すようにしたほうが階層がわかりやすくなります。

ロジック関数で複合関数を呼び出してしまうと、別の水準の関数を使うことになってしまい、階層構造が崩れます。

1つの関数が複数の水準を持たないこと

例えば、1つの関数内に他の関数を呼び出し、ロジックも書くような事をしてしまうと、
複数の水準をもつことになり次にどうなるかがわかりにくくなります。

例えば、以下のような例です。

def func1
  logic1
  logic2
  
  #Logic3 
  if foo
    bar = 1
  end
   
   baz = bar+1
  ...
end

logic1, logic2, logic3は1つ下のレイヤの処理で、logic1とlogic2は別の関数で処理しているのにも関わらず,
logic3はこの関数内で唐突に下のレイヤの処理が書かれています。
こう言うパターンが1つの関数が複数のレイヤを持つ状態で、
ビジネスロジックを追いかけるだけなのに1つ1つの計算式を見なくてはいけなくなります。

階層構造をわかりやすくするために

イメージを作りましょう。
自分の場合こんなイメージでやってます。 f:id:stonebeach-dakar:20161119123819p:plain

慣れてくると自然とこういうイメージが頭に浮かぶようになると思います。

より上を目指すために

各ロジック関数は柔軟に使え、再利用しやすいように部品化しておくことで、他の高レベルレイヤでも使えるようになります。
また、コードの重複を防げるようになるのでDRYなコードになります。

最後に

ソフトウェアが大きくなると、ソースコードの階層構造をしっかり作っておかないと読みにくくなり、メンテナンスが難しくなってしまいます。
また、コードを読む時に要約がないと、読むのに時間がかかってしまいます。

ソフトウェアは何年使われるか予測するのが難しく、自分が理解できていても自分がその製品の担当から外れた時に他のプログラマがコードを読んで
「誰だこんな読みにくいコードを書いたのは!」と内心怒り心頭になったりします。
自分のこと

自分が作ったものに対して誰かが怒っていると知ったらちょっと落ち込みますよね。
逆にあんな読みやすいコード書いてくれてありがとう!と感謝してたら嬉しくなります。

他の人に感謝されるようなコードを書いていこう!
そのほうが精神衛生上いいし

参考文献

プロダクティブ・プログラマ -プログラマのための生産性向上術 (THEORY/IN/PRACTICE)

プロダクティブ・プログラマ -プログラマのための生産性向上術 (THEORY/IN/PRACTICE)

実装パターン

実装パターン

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)

新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES)