プログラミング初心者が中・上級者になるための近道
初心者と中級者、上級者の違いとは何でしょうか?
初心者は、
- 知識が少ない
- 開発したソフトウェアの数が少ない
中級者・上級者はその逆で、
- 知識が多い
- 開発したソフトウェアの数が多い
その結果生まれる実質的な差は、
「初心者はかんたんなものしか作れないけど、中級者・上級者は難しいものを作れる!」
ということです。ですから、初心者が中上級者になるには難しいソフトウェアを作るのに役立つ知識を身につければ良いわけです!
難しいソフトウェアとは、
- ロジックが複雑で難しい
- 規模が大きい
- 性能要件が厳しい
- 納期が短い
など、いろいろな難しさがあります。
これらのハードルに対抗する知識・技術について紹介します。
Contents
規模が大きいソフトウェアを作るための技術
規模が大きいソフトウェアを作るための技術には、以下のようなものがあります。
- モジュール分割
- アプリケーションアーキテクチャ
- フレームワーク
- プログラミング作法
- リファクタリング
1. モジュール分割
規模が大きいソフトウェアを作るためにはモジュールを上手に分割する必要があります。モジュール分割とはかんたんに言えば、ソースコードを役割毎に分けて整理することです。
規模が大きいと、上手に分割整理しないとソースコードが管理不能になってしまいます。
例えば、
- すべてのソースコードを一つのディレクトリに格納した場合
- モジュールごとに分類してディレクトリに格納した場合
では、ソースコードを探す手間が格段に異なります。
では、どのようにモジュール分割すればいいでしょうか?
アプリケーションアーキテクチャを決めれば、モジュール分割の指針が生まれます。
2. アプリケーションアーキテクチャ
例えば、「MVCアーキテクチャでこのソフトウェアを作る!」と決めたら、
- Model
- View
- Controller
の3つにモジュールを分けることができます。
これらのディレクトリを作成してその中にソースコードを格納するようにすれば、Modelのコードを探す時にはModelディレクトリの中を探せば良くなります。
必要であればこの3つに分けた中で、さらにディレクトリを分けていきます。例えば、Modelディレクトリの中をさらに分類するケースを考えてみましょう。ブログシステムの場合、
- Model/Userディレクトリにユーザー機能(ログインやマイページ)を格納する
- Model/Articleディレクトリに記事投稿・表示機能を格納する
のうように分けることができます。こうやってソースコードを適切に分割・整理します。
3. フレームワーク
アプリケーションアーキテクチャを具体的に実装したものがフレームワークです。
例えば、Ruby On RailsはMVCアーキテクチャを実装したフレームワークです。Ruby On Railsの規則に従ってソフトウェアを開発すれば、自然とMVCアーキテクチャによるモジュール分割ができます。
Railsの規則に沿ってプログラム作成をしていけば、自然とmodelディレクトリにモデルのコード、viewディレクトリにビュー、conrtollerディレクトリにコントローラのコードが整理されます。
4. プログラミング作法
大規模なソフトウェアを開発するにはたくさんのプログラマーが必要です。プログラムの書き方はプログラマーによって千差万別です。
100人プログラマーがいて100通りの書き方をされた場合、バラバラの書き方がされたコードが出来上がり、書いた本人以外には読みにくく、メンテナンスが大変になります。そこで、ソースコードの書き方に対する指針を設けます。その指針となるものがプログラミング作法なのです。
そのものずばりなタイトルの「プログラミング作法」という書籍があります。
C言語で有名なカーニハンとロブパイクの共著です。この本では良いソースコードの3原則として
- 簡潔性
- 明瞭性
- 一般性
を挙げています。
簡潔性 ~コードは重複なく短く書きましょう!
「簡潔性」とはソースコードをできるだけ短くすることです。短く書くために重複したコードを共通化します。
同じことを何度も書いたらソースコードが長くなってしまいます。よって、重複したロジックを共通化します。
共通化には主に2つの方法があります。
- 重複したコードをメソッド/関数に切り出して呼び出す
- 重複した処理の流れをフレームワーク化して個別ロジックをポリモーフィズム化する
詳しく知りたい方は初心者でも分かるポリモーフィズムの使い方をご覧ください。
明瞭性 ~コードは分かりやすく書きましょう!
「明瞭性」とは、分かりやすさのことです。そのコードが何をしているかが一目でわかるように書きます。
明瞭なコードを書くには、名前の付け方が重要です。変数名やメソッド名が適切な名前のコードは一目で何をやっているコードなのか分かります。
逆に名前の付け方が適切でない場合、いくら読んでも何をやっているコードなのかわからない、あるいは、誤読してしまうことになりがちです。
例1と例2を見比べてください。これらは同じ処理をしています。
例1
int moge = 0;
for (int hoge = 0; hoge < array.length; hoge++) {
moge += array[hoge];
}
例2
int sum = 0; for (int i = 0; i < prices.length; i++) { sum += prices[i]; }
例1だと名前の付け方が適切でないため、何をやっているコードなのか分かりにくいですが、例2のように適切な名前が付けられていれば一目で価格を合計していることがわかります。
また、例2はコードが意図を語るコードとなっています。このようなコードにはコメントを書く必要がありません。
それに対して例1はコードが意図を語っていません。それを補うためにコメントを書くのは愚策です。コードが意図を語るように意識して書きましょう。
例えば、以下のように例1にコメントを書けば、何をやっているコードかを説明することはできます。
//価格の合計を計算する int moge = 0; for (int hoge = 0; hoge < array.length; hoge++) { moge += array[hoge]; }
ですが、あとでソースコードを変更した際にコメントを変更し忘れるリスクが生まれます。
このようなコードを書くよりも以下のように名前の付け方を工夫したコードを書けばコメントがなくても意図が伝わります。
int sum = calculateSumPrices(prices); int calculateSumPrices(int[] prices) { int sum = 0; for (int i = 0; i < prices.length; i++) { sum += prices[i]; } }
一般性 ~他人と同じようなコードを書きましょう!
「一般性」は奇をてらったコードを書かずに一般的なコードを書きましょうというものです。
プログラマーの中には自分の能力の高さをアピールしたいがあまり、わざと難しいコードを書く人がいますが、いい迷惑です。
趣味などで一人でコードを書く場合にはいいですが、大人数で一つのソフトウェア開発をする場合、他人が書いたコードを読む必要があります。いくら技術的に優れていても、多くの人が読み解けない難解なコードはマイナスにしかなりません。
ですから、「一般的にはどうやって書くかな?」ということを意識して書きます。
わかりやすいのは名前の付け方です。例えば、ユーザーのEメールアドレスを格納する変数名を何にするかを考えてみましょう。
String emailAddress; String email; String eAddress; String ema;
のように、人によって変数名の付け方は変わりますが、どれが一番一般的で誤解がないものでしょうか?
emailAddressかemailでしょう。emaのように単語を短縮しすぎてしまうと、短縮する前の名前が何なのか本人以外わかりません。また、短縮の仕方は個人個人で差が生まれやすいものです。よって、単語を短縮する際には注意が必要です。
とはいえ、短縮を絶対にしてはいけないというものでもありません。あまりに長い識別子だとプログラムが読みにくくなってしまいますからね。
このようなプログラミング作法について書かれた本としては「プログラミング作法」の他に「リーダブルコード」があります。
リーダブルコードはGoogleエンジニアが書いた本です。プログラミング作法は、出版されてから10年以上経っており、内容が少々古く読みにくさがあるのですが、リーダブルコードはそれよりだいぶ後に書かれた本なので、読みやすいかと思います。
5. リファクタリング
良いコードを意識して書くことは重要ですが、納期が迫っていて時間に余裕がない時には、良いコードを書くことよりも、その場しのぎの汚いコードを書いて早く完成させることが求められる場合があります。
後で時間がある時にコードを分かりやすいものへと書き換えればいいのです。これをリファクタリングと言います。
また、きれいに書いたコードであっても、改修を積み重ねていく内に分かりにくいコードになってしまう場合は多々あります。そういう場合にもタイミングを見計らってリファクタリングをすると良いでしょう。
テストの自動化もセットで取り組もう!
リファクタリングをしてソースコードをきれいな形に改変したら、そのコードが以前の仕様と同じように動くかテストが必要です。
その際に、手動でテストしてもいいのですが、プログラムをテストするプログラムを作っておくと便利です。これを「テストの自動化」と言います。テスト自体をプログラムにやらせるんです。
テストの自動化をすると、
- テストする手間が減る(プログラムがテストしてくれるから)
- テストを間違うことがない(プログラムがテストするから人為的ミスがおきない)
というメリットがあります。
ロジックが複雑で難しいソフトウェアを作るための技術
以下のようなものがあります。
- 関心毎の分離
- デバッガ
- ログ出力
1. 関心毎の分離
人間が一度に考えられることには限界があります。複雑なロジックの場合、考えなければならないことが多すぎるので、それらを関心毎の単位で分離(分割)します。
例えば、以下のようなユーザー登録のコードがあるとします。
public void doPost(HttpServletRequest request, HttpServletResponse response) { ArrayList errors = new ArrayList(); String email = request.getParemeter("email"); String name = request.getParemeter("name"); if (email == null) { errors.add("Eメールアドレスを入力してください"); } else if (email.length > 100) { errors.add("Eメールアドレスは100文字以内にしてください"); } if (name == null) { errors.add("ユーザー名を入力してください"); } else if (name.length > 30) { errors.add("ユーザー名は30文字以内にしてください"); } if (errors.size() > 0) { request.setAttribute("errors", errors); return; } Connection con = ConnectionFactory.getConnection(); con.preparedStatment("INSERT INTO User(email, name) VALUES (?, ?)"); stmt.setString(name); stmt.setString( email); stmt.executeUpdate(); con.commit(); request.setAttribute("name", name); request.setAttribute("email", email); forward("/registerComplete"); }
このコードは関心事が分離されていません。
コード内容は以下の3種類に分類できます。
- 入力チェック
- DBアクセス
- 画面表示
それらが分離されず一体になっているのです。ここではメソッドに切り出して関心事を分離してみます(クラスを分けるやり方もあります)。
public void doPost(HttpServletRequest request, HttpServletResponse response) { String email = request.getParemeter("email"); String name = request.getParemeter("name"); ArrayList errors = checkInput(email, name); if (errors.size() > 0) { request.setAttribute("errors", errors); return; } registerUser(email, name); fowardPage(request, email, name); } private ArrayList checkInput(String email, String name) { if (email == null) { errors.add("Eメールアドレスを入力してください"); } else if (email.length > 100) { errors.add("Eメールアドレスは100文字以内にしてください"); } if (name == null) { errors.add("ユーザー名を入力してください"); } else if (name.length > 30) { errors.add("ユーザー名は30文字以内にしてください"); } return errors; } private void registerUser(String email, String name) { Connection con = ConnectionFactory.getConnection(); con.preparedStatment("INSERT INTO User(email, name) VALUES (?, ?)"); stmt.setString(name); stmt.setString( email); stmt.executeUpdate(); con.commit(); } private fowardPage(HttpServletRequest request, String email, String name) { request.setAttribute("name", name); request.setAttribute("email", email); forward("/registerComplete"); }
このように関心事を分離すると、ソースコードが読みやすく理解しやすくなるし、実装する時にも分離した小さい単位のロジックに思考を集中できるので、コードが書きやすくなります。
複雑で難解なロジックも関心事の単位に分離していけば、最終的には、小さくてかんたんなロジックの集まりとなります。小さなロジックをひとつひとつ実装していくことはかんたんです!
2. デバッガ
プログラムが思った通りに動かないことはよくあります。そんな時は、デバッガの出番です。大抵のIDEにはデバッガがついていて、ソースコードの指定した位置にブレークポイントを張ることができます。ブレークポイントとはプログラムを途中で止める位置(コードの行番号)のことです。
ブレークポイントを張った状態でプログラムを実行するとブレークポイントを張ったコードの行でプログラムを一時停止することができます。一時停止した状態でその時の変数の値を確認することができます。
また、ブレークポイントで止めた位置から一行ずつコードを実行することもできます。なので、プログラムの挙動を一つ一つ確かめることができます。一行ずつ実行することができることはメリットでもありますが、時間が掛かります。例えば1万回のループを一行ずつステップ実行したらむちゃくちゃ時間が掛かってしまいます。そこで次に紹介するログ出力の出番です。
3. ログ出力
ログ出力とは、変数の値を確認したい箇所に、その変数を出力するログ出力命令を書くことによって、プログラムの挙動を確認する方法です。
例えばこんな感じのコードです。
・元コード
for (int i = 0; i < results.length; i++) { ResultData result = result[i]; if (result.price > 100000 && result.discount == true) { result.msg = "お買い得"; } }
・ログ埋め込み後
for (int i = 0; i < results.length; i++) { ResultData result = result[i]; if (result.price > 100000 && result.discount == true) { result.msg = "お買い得"; logger.info(i + "件目のデータ price=" + result.price); } }
ログ出力させているコードはこの部分です↓
logger.info(i + “件目のデータ price=” + result.price);
配列のインデックス番号とデータの中身を出力させています。
ログ出力のいいところは結果の確認が早いことです。この例でいうとresults配列の個数が1万件あったとしてもプログラムの実行は一瞬です。実行後、出力されたログを確認します。実行も確認も早くできるのですが、デメリットもあります。
それは、いちいちログ出力のコードを書かなければならないということです。どこにログ出力を入れてどの情報を出力させるかは自分で決めなければなりません。無意味な場所にログを仕込んでも問題は解決できませんからね。
そのため、どこにログを仕込めばいいかが分からないくらいコードの動きが把握できていない場合は、デバッガを使った方が良いでしょう。
また、見たい情報の項目が多い場合、ログにも出力項目をたくさん書かなければなりません。この場合もデバッガの方がいいでしょう。
まとめると以下のようになります。
デバッガを使うべき時 | ログ出力を使うべき時 |
---|---|
見たい情報が決まっていない時にも使える | 見たい情報が決まっている時 |
見たい情報の項目数が多い時 | 見たい情報の項目数が少ない時 |
回数の多いループの中は見ない時 | 回数の多いループの中を見たい時 |
状況に応じて使い分けましょう!
性能要件の厳しいソフトウェアを開発する技術
以下のようなものがあります。
- アルゴリズム
- 並列化
1. アルゴリズム
性能の良さには、2つの尺度があります。
A. 使用するメモリー量ができるだけ少ないこと = 軽量
B. できるだけ短い時間で処理が完了すること = 高速
軽量で高速に動作するプログラムが一番ですが、多くの場合これは相反します。
- メモリーをたくさん使えば速く処理できる
- 仕様メモリーを減らすと処理時間が増える
のように、「こちらを立てればあちらが立たず」というトレードオフの関係になる場合が多いんです。
また、性能を測る尺度としてオーダーという概念があります。
オーダーとは解に達するまでに必要な操作回数のことです。
たとえばデータ探索のアルゴリズムの場合、n個のデータを前から順番に辿っていく線形探索の場合オーダーはn回です。これをO(n)と書きます。これは要素の個数が増えると計算量も比例して上がってしまいます。そのため大量のデータから探索する場合、計算量が思いっきり上がってしまうため良い性能が出ません。
それに対して二分探索の場合、O(log2n乗)なので、要素数が増えても計算量はそれほど上がりません。
アルゴリズムによってオーダーや使用するメモリー量の特性が分かるので、システムの性能要件に合ったアルゴリズムを選択すると良いでしょう!
2. 並列化
いくら速いアルゴリズムを選択しても、一台のコンピュータで出せる性能には限界があります。一台のコンピュータで求められる性能が出せない場合も処理を複数のマシンに分散して並列に処理することができれば、求められる性能を出せる場合があります。
また、最近のCPUはCPUコア一つにおける性能向上が限界に達していて、コアを複数にすることで性能向上させています。マルチコアCPUの性能を引き出す時にも処理をコアごとに分散して並列処理させる必要があります。ですから、現代のプログラマーにとって、並列処理は重要な課題なんです。
マルチコアについてはコードの未来という書籍でRuby開発者のまつもとゆきひろさんがとてもおもしろいことを書いていました。
- 今までは一度ソフトウェアを作って数年たったらCPUの性能が上がって何もしなくてもソフトウェアの実行速度は勝手に上がった。つまりフリーランチがあった(プログラムを改善しなくてもハードウェアの力で性能が上がったという意味)
- 現在はCPUの1コア単体の性能向上が限界に達したため、マルチコア化して性能向上させるようになった。
- そのため、ソフトウェアがマルチコアを活かすように作っていないとCPUが性能アップしてもソフトウェアの性能が向上しない。つまりフリーランチはなくなった。
という話です。とても楽しく読めて、勉強になった本なのでおすすめです。
納期が短いソフトウェアを開発する技術
納期が短いソフトウェアを開発するには生産性を高める必要があります。生産性を高める技術として以下のようなものがあります。
- 抽象度の高いプログラミング言語
- IDE
1. 抽象度の高いプログラミング言語
例えばある数値配列の中から値が4以上の要素を集めるという処理を(バージョン7までの)Javaで書くと、
int[] list = {4, 9, 3, 1, 5, 2, 7, 6}; ArrayList results = new ArrayList(); for (int i in list) { if (i >= 4) { results.add(i); } }
となるのがRubyなら、
list = [4, 9, 3, 1, 5, 2, 7, 6] results = list.select {|item| item <=4 }
で済みます。
Javaだと7行必要な処理がRubyだと2行で書けています。
とはいえJavaもバージョン8からストリームAPIというものが実装されてRubyに近い書き方ができるようになったので、以前よりは簡潔に書けるようになったんですけどね。
言語によって、1行で表現できるロジックが大きく違います。少ないコードでたくさんのことができる言語を選んだ方が生産性は上がります。
一つの言語をずっと使い続けて極めるのも良いですが、他にもっと良い言語はないか関心を払うことも大切です。
2. IDE
IDE(統合開発環境)とはエディタやコンパイラなど開発に必要なものをまとめて提供してくれるツールのことです。
- Visual Studio
- Eclipse
- NetBeans
- InteliJ
などがあります。
IDEに付属しているエディタを使うとコード入力の支援を受けられます。
例えば、Eclipseで
String.
と打つとStringクラスが持っているメソッドやフィールドが一覧表示されます。一覧から呼び出したいメソッドを選択すると、そのメソッドがコードに足されます。なので、クラスのメンバーを暗記しなくても済みますし、コードをタイプする手間が省けます。
このようなプログラマーを支援する機能がIDEにはたくさん備わっています。しかもIDEはどんどん進化していて、以前ならば自分の手で打ち込んでいたコードがどんどん自動入力されるようになっています。
また、IDEによって、実装されてる機能は異なるので、言語同様、慣れているものばかりずっと使うのではなく、より良いものがないか探すことも大切です。
一つ一つ取り組めば、それほど難しくない!
いろいろと紹介しましたが、どれも一つ一つはそれほど難しいものではなく、やればマスターできるものばかりです。
これらを一つ一つ取り組んでいったら、ある時、自分も上級者になっていた、そういうものなんだと思います。
ですから、紹介したものの中に知らないものがあった人はぜひ取り入れてみてください!