(文責:岸)
プロトタイピング時のCUI版個別開発において、 私たちは、同時にソースコードレビューを繰り返した。 その中での議論をコード規約としてまとめた。
目的は、 チームが共同で作るソースコードに一貫性のある与え、ソースコードを分かり易く、保守・拡張しやすくするためである。 また、問題になるコードの書き方のいくつかを防ぐことができ、生産性も向上するだろうと考えた。
コード規約の作成方法は、CUI版の個別実装物に関して、開発メンバーおよび松澤氏で問題となるであろうコードを指摘し、 解決策を議論し、それらを後にドキュメント化した。 もともとの問題点がわかるように、ほとんどの規約に、問題となったソースコードの一部を抜粋して掲載した。 また、開発メンバー間でもともと統一されていた書き方については、触れていない。 コード規約のドキュメント化には
などを参考にした。実は、コード規約は本実装においてはチェックする役割をもった人がいなかったので100%守られたわけではないが、 それでも、ソースコードにある程度の可読性を与えた。 そして、仕様変更による修正やデバッグでは、その効果を実感できた。
以下に作成したコード規約を示す。
取り決め
・[構成 ] 1 【ファイル先頭コメント(省略可能)】 2 package文 3 import文 4 【クラス先頭コメント】 5 クラス定義 6 【フィールド・コメント】 7 フィールド定義 8 【メソッド・コメント】 9 メソッド定義 ・[クラス先頭コメント] クラスに対するコメントは次のようにする ------------------------------ /** * クラス名 * (クラスの説明や仕様省略可) * * @author 著作 * @version $id$ */ public class Xxxxx{ ------------------------------- ・1行目:"/**"改行 ・2行目:以降"*" ・最終行は"*/" ・著作やバージョンの前は空行 ・バージョンはCVSのキーワード$id$を用いる※ ・クラスコードとクラスコメントの間は改行しない ・クラスの説明や仕様は省略可。 ※CVSコメント自動挿入について チェックアウト時に情報が自動的に書き換えする。 キーワード $id$ ファイル名、リビジョン番号、コミットした日時、コミットした者の名前 $Revision: 1.1 $ リビジョン番号を挿入 $Author: gackt $ コミットした者の名前を挿入 ※主なJavadocタグ(クラス/インタフェース用) @author 著者名 @version バージョン @see 関連クラス名 @since このクラスが導入されたバージョン
/** * @author KISHI Kenji * 2003/05/1412:37:42 * */ package mirumiru.model; import java.util.*; public class Answer {
問題点
クラスコメントがクラス宣言と離れ、何のコメントであるかわからない。 クラス名がなく、何のクラスであるか判断できない。 JavaDoc(自動でAPIドキュメントを生成するツール)に未対応である
package mirumiru.model; import java.util.*; /** * 回答クラス * * @author KISHI Kenji * @version $Id: index_c11.html,v 1.1 2005/04/07 07:28:19 gackt Exp $ */ public class Answer {
取り決め
・クラス内の構成 1.属性(クラス変数static→インスタンス変数の順) 2.コンストラクタ 3.メソッド(機能が似ているものをまとめて記述) の順に記述し、間にセパレータになるコメントを入れる ・分けるセパレータは 改行 /************ * XXXXX *************/ 改行 などとする. (分けたセパレータの前後は1行空ける) ・メソッドはメインのもの、大きなものを上に記述 メインメソッド ↓ サブメソッド ↓ …
public class Answer { public static final String NOANSWER="未回答";//未回答の内容 private String content;//内容を保持 private Hashtable studentTable;//この回答をしている生徒一覧 private String id;//属する質問の中でユニーク Answer(String id,String content){ this.content=content; this.id=id; studentTable=new Hashtable(); } //この回答に学生を加える public void addStudent(Student student){ studentTable.put(student.getId(),student); }
問題点
どこで何をしているか把握しづらい
public class Answer { /******************************** * 属性 ********************************/ private String content; //内容("YES","1時間以内"など) … /******************************** * コンストラクタ ********************************/ /** * コントラクタ */ public Answer(String content) { this.content = content; } /******************************** * 学生関連 ********************************/ /** * 回答者に学生を加える */ public void addStudent(Student student) { students.add(student); } … /******************************** * 内容関連 ********************************/ …
取り決め
クラス内の変数宣言が多いときは 属性と関連の2ブロックに分ける /************* * 変数 **************/ //属性 【属性の宣言】 //関連 【関連宣言】
/*************** * 属性 ***************/ private String Name; private String teacher; private String password; private Question currentQuestion;//現在の質問 private ArrayList students; boolean state; }
問題点
どんな変数があるか把握がしづらい
/*************** * 変数 ***************/ //属性 private String Name; private String teacher; private String password; boolean state; //関連 private Question currentQuestion;//現在の質問 private ArrayList students;
取り決め
他の人が理解困難な属性の説明に具体例を入れる ※フィールドはカプセル化されるため、外部クラスから参照されない。つまり保守者向けのコメントが要求される。
public class Answer { … private String content; //内容
問題点
「Answerの内容って何だ?」と感じてしまう。
public class Answer { … private String content; //内容("YES","1時間以内"など)
取り決め
1)メソッド先頭コメント形式は /** * メソッドの説明(や仕様) */ public int XXX(String XXX){ とする。 ・1行目:"/**改行"、2行目以降"*"で開始 ・コメント最終行は"*/改行" ・メソッドとコメントの間は空行を入れない ・仕様は省略してよいが、メソッドの引数、戻り値、例外、スレッドの安全性、事前条件、事後条件などが考えられる。 2)メソッド内部コメント 難しく複雑な処理にはつける。 コードがする処理内容と共に、コードの目的やなぜそのようにかかれているかの理由が重要。この方が保守する人にはありがたい。 3)段落化: 意味のある処理の単位ごとに 空行を入れ固まりを作り、 それぞれの先頭に"//"コメントを入れる //処理1の意味(説明や目的) ------------------------ 文; 文; //処理2の意味(説明や目的) 文; 文; … ------------------------- ※主なJavadocタグ(コンストラクタ/メソッド) @param 引数名 説明 @return 説明 @see 関連クラス名 @since 導入されたバージョン
//新規N択問題用コンストラクタ public Question(int n){ //選択肢の登録 Answer ans; for(int i=1;i<=n;i++){ String ansId=String.valueOf(i); ans=new Answer(ansId,i+"番"); answerTable.put(ansId,ans); } //内容登録 content="質問です"; //パターン名登録 patternName=n+"択"; }
問題点
処理ブロックとメソッドがぱっと見たとき判別できない。 構造が理解しづらい。 javadoc未対応
/** * 新規N択問題用コンストラクタ */ public Question(int no) { //未回答を登録 Answer noAnswer = new Answer(null); answers.add(noAnswer); //選択肢の登録 Answer ans; for (int i = 1; i <= n; i++) { ans = new Answer(String.valueOf(i) + "番"); answers.add(ans); } //内容登録
注意:タイピングの手間は関係ない。読む人のために決める。
取り決め
for文のiや座標xyzなど誰もが認めるもの以外 1文字変数名を避ける
public Question(int n) {
問題点
「n」が何を意味するのかわかりづらい
public Question(int number) {
取り決め
了承がない省略単語は変数名に含めない
private int passwd; private Stirng sName;
問題点
「wdってwordの略だよね?」 「sNameってstudentName?」などと推測が必要
private int password;
取り決め
属性に直接セットされる引数の名前は属性と同じにする
public Lecture(String string, String string2, String string3) {
name = string;
teacher = string2;
password = string3;
…
public void setContent(String title) { content = title; }
問題点
引数の名前が指すものがわかりづらく、混乱する可能性がある
public Lecture(String name, String teacher, String password) { this.name = name; //このクラスのname=引数のname this.teacher = teacher; this.password = password; …
取り決め
リストを扱う変数/メソッド名は リストを扱っていること明示する。 明示手段は 名詞複数形にしたものを利用。 (利用困難時は"○○List"を利用?)
private ArrayList student; … public ArrayList getStudent(){ return student; }
問題点
変数定義は学生のリストであることが伝わらない メソッドはstudentが1つ返ると勘違いしてしまう。
private ArrayList students; … public ArrayList getStudents(){ return students; }
取り決め
メソッド名は動詞から始まる
public studentLogout( …) {
問題点
メソッド名であることがわかりづらい 覚えづらい/予想しづらい
public makeStudentLogout(…)
取り決め
変数名・定数名・クラス名の統一をする teacher…授業の担当者名 password…パスワード id…学籍番号(小文字) ACTIVE…授業のサポート開始状態 INACTIVE…授業のサポート終了状態 alternative…選択肢クラス
ecturer/teacher…授業の担当者名 passwd…パスワード number/id…学籍番号 ON / OPEN…授業のサポート開始状態 OFF / CLOSE…授業のサポート終了状態 answer/choice…選択肢
問題点
実装者によっての名付けのばらつきがあり、 混乱を来す恐れがある
インデントや空白はEclipseのデフォルトのコード整形(フォーマット)で整形する。
取り決め
1文中で「.」ドットやキャストを沢山使わない
((Answer) currentLecture.getCurrent().getChoices().get(1)).setContent("Yes");
問題点
改変が困難 処理内容が把握しづらい nullポインタExeptionが出てもスルー箇所が特定しづらい
Question currentQ=currentLecture.getCurrent();//カレント質問取得 Answer ans1=(Answer)currentQ.getChoices().get(1);//1番目の選択肢取得 ans1.setContent("Yes");
プログラミング上の注意, 良いプログラムを書くためのテクニック, バグを防ぐ工夫, イディオム などについて いわば、ノウハウ集
取り決め
・privateのリストを渡すときはコピーを渡す (コピー例) return new ArrayList(リスト); ・リストの追加/削除などの編集をするときは 外から直接でなく、追加/削除メソッドを呼ぶ
private ArrayList students = new ArrayList(); //参加生徒一覧 … /** * 参加学生のリストをかえす */ public List getStudens() { return students; } …
問題点
クラス外からリストがいじられる可能性がある。
private ArrayList students = new ArrayList(); //参加生徒一覧 … /** * 参加学生のリストをかえす */ public List getStudens() { return new ArrayList(students); } /** * 参加学生を追加する */ public void addStudent(Student student) { students.add(student); } …
取り決め
内部のデータ構造に依存せず外からアクセスできる方式(メソッド化)にする
/** * 回答を取得する */ public getAnswer(int index){ return (Answer)answers.get(index); } ※別オブジェクトからgetAnswer(0)として未回答を得る
問題点
内部のデータ構造に依存してアクセスしている →内部実装が変わったら外部のクラスも変更しなくてはいけない
** * 未回答を取得する */ public Answer getNoAnswer() { return (Answer) (answers.get(0));※注)リストanswersの0番に未回答が入っている } -------------------------------------------- /** * 未回答を取得する */ public Answer getNoAnswer() { return noAnswer;※注)noAnswerに未回答が入っている }
取り決め
・状態は生の値で扱わず、定数を定義する。 ・将来状態が3つ以上になる可能性が否定できない場合、booleanを利用せず、int型にする ・定数は大文字
private boolean state=false;//実施中か実施してないか
問題点
falseがどんな状態であるか他者にがわかりづらい。 仕様変更にも弱い
//実施状態の定数定義 public static final int ACTIVE = 1; public static final int INACTIVE= 0; … private int state = CLOSE; //サポート実施中か実施してないか
取り決め
while/for/switchなどの{ }ブロック処理が長くなるときは、 部分的にメソッド化するなどして1画面(40行程度)ぐらい に納める
while(true){ … 100行以上の処理 …
問題点
全体の流れが見えづらい
取り決め
モデルでなくアプリにある ユースケースに対応する様なメソッドで モデルの整合性を取る
public class MirumiruApp{ … /** * 新規n択出題する */ public void setNewSelectQuestion(String lectureName, int number) { Lecture lecture = (Lecture) lectureTable.get(lectureName); Question question = new Question(number);//新規質問をつくっている lecture.setNewQuestion(question);
public class Lecture { … /** * 新規質問の出題 */ public void setNewQuestion(Question question){ question.start(studentTable);//参加者を全員未回答にし、質問を回答出題状態にする questionList.addElement(question); } …
問題点
・「新規出題をする」実現時の 整合性を取る処理が あちらこちらに分散。把握が難しい。 ・変更困難。
取り決め
コンストラクタ中でそのオブジェクトのインスタンス変数を初期化するかたちに統一する。
public class Answer implements Serializable { … private ArrayList students = new ArrayList(); …
問題点
インスタンスの変数を初期化するとき、 人によって書き方が違ったり、入り交じったりと、 どこで初期化しているかの予測がつきづらい。 上の書き方はコードとしては問題ない。
public class Answer{ … private ArrayList students; … /* * * コンストラクタ */ public Answer(String argAcontent){ … Acontent; students = new ArrayList(); }
取り決め
扱うリストはArrayListに統一。 (Listインタフェースによるポリモーフィズムは今回気にしない。若干背伸びするように感じられたため。)
/** *回答リストを取得 */ public Vector getAnswerList(){ Vector res=new Vector(); res.addElement(getNoAnswer()); res.addAll(((Collection)(getSelectionAnswerList()))); return res; }
問題点
外部から利用するとき、何のリスト(ArrayList,Vector,LinkedListなど)で実装されているかを気にしなくてはいけない。
/** * 学生リストを得る */ public ArrayList getStudents() { return new ArrayList(students); }