「コーディングを支える技術」を読みました(1)
勉強記録
今日は「コーディングを支える技術」を読みました。
第1章と第2章は軽く読み、第3章から重要そうなところをまとめてみました。
<第3章> 文法の誕生
文法とは?
文法とは、「こう書いたら、こういう意味に解釈する」というプログラミング言語の設計者が決めたルールのこと。
構文木
最近の言語でも構文木は使われている。
astライブラリを使うことで、特定のコードがどういう構文木になるのかがわかる。
構文解析器(パーサ)
構文解析器のことをパーサと呼ぶ。
<第4章> 処理の流れのコントロール
構造化プログラミングの誕生
1960年代後半に、人間がプログラムを楽に読み書きできるようなルールを作ろうという流れになり、「構造化プログラミング」が誕生した。
ifやwhile文などのルール(構文)を導入することで、コードの構造をわかりやすくしようとした。
if ……. elseはなぜあるのか
C言語ではelseを使う代わりに、gotoを使う方法がある。
gotoの機能は「指定された行にジャンプする」というシンプルでわかりやすいもの。
しかし、条件が真または偽の場合に処理が分岐する場合は、if ……. elseを使ったほうがわかりやすい。
このような経緯があって、手軽で読みやすくするためにif ……… else構文というルールが導入された。
whileを使わない表現方法
while文は条件を満たしている間、ブロックの中身を繰り返し実行するという構文。
しかし、while文がなくても同じような処理はかける。
while文もbreak文もgoto文でできることをやっている。なぜwhile文があるのかというと、新しいできることを増やすという目的ではなく、読みやすさや書きやすさのためにある。
つまりif …… elseやwhile、breakは「制限付きのgoto」であると言える。
foreach
for文を進化させたのがforeach構文。
ある対象の要素全部について何か処理をするというコードを手軽に書くことができる。
わかりやすいコードにするためには
if文、while文、for文などは処理の流れをコントロールするためのルールである。
だから、使わなくてもプログラムを書くことはできるが、使った方が簡単にわかりやすいコードを書くことができる。
<第5章> 関数
関数の役割
関数は「理解」と「再利用」のために必要である。
理解とは?
ソースコードの行数が多くなってくると、全体を把握するのが難しくなる。
関数を利用すれば、いくつかの行をひとかたまりにして、それに名前がついているので、コードを理解しやすくなる。
プログラムにおける再利用の特徴
同じ処理を1ヶ所にまとめるメリットは、プログラムが短くなるというだけではない。
結果、よりプログラムが理解しやすくなる。
再帰呼び出し
再帰呼び出しとは、ある関数Xの中から関数X自体を呼び出すこと。(自分自身を呼び出す処理が書かれている関数を呼び出すこと)
かつては再帰呼び出しができない言語もあったが、今ではほとんどの言語でできる。
入れ子構造を扱う方法
入れ子になった処理とは「ある手続きをやっている最中に同じ手続きを違う対象について行う」ということ。
たくさん入れ子になったデータ構造は珍しくはない。
そういったときに再帰呼び出しを使う。
<第6章> エラー処理
プログラムも失敗する
プログラムも失敗するときがある。例えば、ファイルに書き込もうとしたときにディスクがいっぱいで書き込みに失敗するなど・・・
そういった失敗が起こったときに、何も警告されないとユーザーは失敗に気づきにくくなってしまう。
プログラム言語にも「失敗を伝えるしくみ」が必要である。
失敗をどうやって伝えるのか
エラー処理の書き方は大きくわけて2つ。
呼び出し元が返り値をチェックしてエラー処理をする方法。
関数が返す値を「成功したときは0を返し、失敗したときはそれ以外を返す」と決めておく方法。
返り値が0以外であれば、エラー処理をする。
しかし、この方法は2つの問題がある。
1.プログラマが返り値をチェックし忘れたときに、失敗を見落としてしまう。
2.エラー処理のせいでコードが読みづらくなってしまう(本来やりたいことを書いたコードの中に、エラー処理のためのコードがたくさん挟まって流れが読みにくい)
関数を呼び出す前にエラー処理のコードを登録して、失敗時にエラー処理コードにジャンプする方法。
前者は今でもC言語などでよく使われている手法で、後者が例外処理と呼ばれている手法。
それぞれのメリット、デメリットを把握して、適材適所で選ぶことが大切。
失敗しそうなコードを囲む構文
Javaなどの構文では例外処理の構文とは大きく違って、「失敗しそうなコードを先に(try{…….}で囲って)書いて、それから失敗したときの処理を書く」という形をしている。
対になる処理を確実に行いたい
プログラミングには「対になる処理」がたくさんある。
例えば、メモリを確保してあとで解放する、ファイルを開いて後で閉じるなど、片方の処理を実行したら、もう片方の処理も確実に実行したいということ。
finallyによる解決
finallyブロックは、処理がtryブロックから離れるときに、必ず実行される。
どういうときに例外を投げればいいのか
try/catchで囲む例外処理の構文は、「例外が投げられたときにどうやって処理をするか」という視点で書く。
では、どういうときに例外を投げればいいのか。
間違えたらすぐに例外を投げる
例外のメリットは「失敗を見逃さない」ということ。
「おかしくなったら処理を停止して速やかに報告すべき」という設計しそうはフェイルファーストと呼ばれている。
例外の伝搬
多くの例外処理では、例外が呼び出し元に伝搬する。どの関数でも処理できなければプログラムが異常終了する。
どういう例外を投げるかを明示的に宣言することが必要。(Javaはこの方法を採用している)
つまり、「例外」と呼んでいるものをさらに3つに分類している
1. 例外処理すべきではない重大な問題
2. 例外処理をしてもよい、実行時例外
3. 例外処理をしてもよい、それ以外の例外
3に関しては「検査例外」と呼び、メソッドの外に投げるのであれば、それをメソッドの定義時に宣言する必要がある。
検査例外が普及しない理由
検査例外を使うと、うっかり例外を投げる可能性を見落としたということが起こらなくなるが、あまり他の言語に普及していない。(一番の理由はめんどくさいから)