失なわれた構造化プログラミング、そしてオブジェクト指向へ…
この記事はフィヨルドブートキャンプ Part 2 Advent Calendar 2020 - Adventarの18日目です。 昨日は同じくメンターの10ヶ月フィヨルドブートキャンプのメンターをして感じたプログラミング学習中に落ち込みやすい3つのこと|りほ|noteの記事でした。
初学者ほど学習内容の詳細よりモチベが大事なのでエモい話役に立つよな!って思いました。
で、僕はめっちゃ技術系の細かい話です😇
前提
フィヨルドブートキャンプの現役生特に初学者、プログラミングに入りたてぐらいの方向けの記事です。
メンターをさせて頂いていて、よく指摘する内容だけど、この部分だけ教える事はほぼ無いので書きました。
(主にフィヨルドブートキャンプ生に向けた)注意事項
- ブートキャンプの課題のネタバレにならないサンプルを考えようとは思ったのですが、手間だったのと分かりやすいのでlsコマンドの例をそのまま使います。 模範回答でも無いし入力関係の一部しか使ってないので見ても良いとは思いますが、気になる方は課題をクリアしてから見てください。
- サンプルはよく見るパターンを抜粋、簡略化したものを私が書いたものです。似たコードを書いた事のある人が居るかもしれませんが、貶める意図はありません。
- 構造化プログラミング、オブジェクト指向についてのエキスパートと呼べるほどでは無いのでおかしな記述をしていたら指摘を頂けると嬉しいです。
- 日本語も入っているし実行していないので間違っているかもしれません。rubyで書いてるつもりですがrubyっぽい仮想言語のコードと思ってください。
はじめに
皆さんは構造化プログラミングという言葉を知っているでしょうか?
オブジェクト指向プログラミングがデファクトになる前に存在した古の手法でインターネット老人会員しか知らないと思います。
僕がプログラマになったときは構造化プログラミングが普通でオブジェクト指向ってのがあるらしいぞ?ってぐらいの時期だったので、オブジェクト指向を学んだときに「なるほど!部品となるサブルーチンにデータもまとまって独立すんのか!メンテナンス性上がるわ!すげえ!」とか感じたわけですが、最初からオブジェクト指向だと、なんで動くのにオブジェクトとかメッセージとか言ってんだよ意味わかんねーよ!ってなるよなぁーって思ってたのでこの記事を書きました。
オブジェクト指向でなぜつくるのか 第2版 | 平澤 章 | コンピュータ・IT | Kindleストア | Amazon
もちろんこの本を読めば大体分かるとは思いますが。
今さら教えるべき!とは思いませんが、オブジェクト指向で作ってはいるけどそもそも構造化ができてないなーと思う事があります。(ブートキャンプ生等初学者はもちろん仕事でもたまに)
詳しい内容は 構造化プログラミング - Wikipedia 、プログラミング入門入門 - けるぶれつーるず あたりの説明を見てもらえると良いのですが、私が重要と思っているのが構造化と再利用、要するに「再利用可能な機能に切り出して部品化して順番に呼び出す」という所です。これを簡単な例を用いて解説します。
構造化されていないコード
フィヨルドの課題にあるlsコマンドのlオプションのみ対応してファイルを複数引数で取る(無いときもある)rubyプログラムです。
入力まわり以外は省略していますし、便利ライブラリも使っていません。
処理省略してるし、慣れた方にはこんなコード書かねぇよ!ってつっこまれそうですが、最初は色々書いてると混乱してこんな風になったりすると思います。
# オプションlがある
if ARGV[0] == 'l'
if ARGV.count > 2
# 引数があるときの情報取得、整理
else
# 引数が無いときの情報取得、整理
end
else
if ARGV.count > 1
# 引数があるときの情報取得、整理
else
# 引数が無いときの情報取得、整理
end
end
if ARGV[0] == 'l'
if ARGV.count > 2
# 引数があるときの結果表示
else
# 引数が無いときの結果表示
end
else
if ARGV.count > 1
# 引数があるときの結果表示
else
# 引数が無いときの結果表示
end
end
オブジェクト指向にしようとして失敗したコード
オブジェクト指向よくわからんけど、とりあえずクラスを使って書いてみよう…ぐらいで書くとこうなります。ちょっと極端ですが。
クラス名がついているので処理のグルーピングはできていますが、見ての通り中身は上とほぼ同じです。クラス使ってる意味無いですよね。
# オプションlがある
class Calculator
def call
if ARGV[0] == 'l'
if ARGV.count > 2
# 引数があるときの情報取得、整理
else
# 引数が無いときの情報取得、整理
end
else
if ARGV.count > 1
# 引数があるときの情報取得、整理
else
# 引数が無いときの情報取得、整理
end
end
end
end
class Formatter
def call(calc_result)
if ARGV[0] == 'l'
if ARGV.count > 2
# 引数があるときの結果表示
else
# 引数が無いときの結果表示
end
else
if ARGV.count > 1
# 引数があるときの結果表示
else
# 引数が無いときの結果表示
end
end
end
end
calc_result = Calculator.new.call()
Formatter.call(calc_result)
構造化プログラミングで整理したコード
さて、本題です。要点は以下。
- 処理は部品化する、意味のあるまとまりで分割して重複した処理を作らないようにする
- それぞれの部品はできるだけシンプルにする
- それぞれの処理は入出力を明確にして、より前の入力のみを引数として渡して出力を得る
def parse_input(args:)
option_l = args[0] == 'l'
if option_l
[true, args[1..-1]]
else
[false, args[0..-1]]
end
end
def calc(files:)
if files.length > 0
# 引数があるときの情報取得、整理
else
# 引数がないときの情報取得、整理
end
end
def format(calc_result:, option_l:)
if option_l
# option_lがあるときの結果表示
else
# option_lが無いときの結果表示
end
end
# 入力を整理して、扱いやすい変数に入れる、ここを分けずに下のcalcと一緒にしてしまうとごちゃつきがちになります
option_l, files = parse_input(args: ARGV)
# この処理に必要な情報のみ引数にする。
calc_result = calc(files: files)
# outputするメソッドにしても良いけど、入出力がはっきりしたものの方がテストしやすいのでformatのみにした
puts format(calc_result: calc_resul
クラスは使わなくても理解しやすいシンプルなものになる事が分かると思います。
この例では一回ずつですが、入出力がはっきりしているので、追加の仕様(オプションが増えるとか)ができたときは修正しやすいですし、再利用できるケースもあるかもしれません。
ちゃんとオブジェクト指向にしたコード
それこそネタバレ感満載なので割愛。
構造化プログラミングで整理したものをベースにすれば簡単なはず。
構造化をした上で、メソッドとデータをセットにできるのがオブジェクト指向の利点と考えると理解しやすいと思う。
ただ、このぐらいの単純な例だとオブジェクト指向にした方が読みづらくなると思いますが、規模が大きいものの為にできた概念なので普通です。
まとめ
構造化プログラミングはオブジェクト指向の前からあっただけにより直感的で理解しやすい手法です。
アジャイルを学ぶときにウォーターフォールの知識があった方がアジャイルの良さが分かるように、構造化プログラミングで部品化して整理する事を知っておくとオブジェクト指向の良さの理解も深まると思います。
もちろんオブジェクト指向をしっかり学んで習熟すれば構造化のステップは踏まずにより良いコードにできるし、今さら必須教科とは思いませんが、初学者は一回構造化のステップを踏んだ方が分かりやすいケースもあるんじゃないかと思っています。
構造化プログラミングもオブジェクト指向も、結局のところコンピュータに対する命令であるプログラムに同僚や未来の自分というプログラムを修正する人への理解しやすさを付加するものであると思っていて、職業プログラマの私としては一番大事にしている所です。
理解しやすさを大事にしているので、人が書きやすい、読みやすい事にこだわった言語であるrubyが私は大好きです。
この記事が皆さんが読みやすいコードを書く為の一助になれば幸いです😄
Discussion