Upload
eyes-japan
View
223
Download
7
Embed Size (px)
Citation preview
1
わかるコードを書くために
For writing clean code
2
$ whoami
青木 太郎
Eyes, JAPAN / ヅ大 2年目
Groove Coaster
STEINS;GATE
色々...
3
わからないコードs = a(p.h);
function a(b) { return b * 900 + 4000; }
4
4
5
目次
1. 目的
2. 表面の改善
3. 内面の改善
4. まとめ
6
1. 目的
7
主な対象者
一人でコードを数千行書いたことのない人
8
プログラミング
コードを読む
コードを書く
9
コードを読む時間のほうが書く時間より圧倒的に多い
プロジェクトのコードの処理の把握
バグの発生場所の検証
ライブラリなどのドキュメント
10
コードが汚いと…
誰も読めなくなる
保守性が下がる
バグが混入しやすくなる
楽しくない
11
きれいなコードを書けると…
自然と誰が見ても分かりやすいコードになる
嬉しくなってテンションが上がり、ゾーンに突入できる
12
一人で作業するならきれいなコードを書く必要はない?
13
一人で作業するならきれいなコードを書く必要はない?
数週間後の自分は他人
14
数週間後の自分は他人
15
2. 表面の改善
16
コーディング規約殆どの言語でコーディング規約が存在する
17
17
18
代表的な表記法
名前 例
アッパーキャメルケース(UCC) ExampleString
ローワーキャメルケース(LCC) exampleString
スネークケース example_string
ハンガリアン記法 mExampleString
19
わりと一般的な命名規則の例
名前 表記 例
クラス UCC class ImageUtil {...}
変数、メンバ変数 LCC or スネークケース let imageTitle;
関数、メソッド LCC function fetchImageTitle() {...}
定数 大文字 + スネークケース const MAX_STRING;
20
なぜコーディング規約を守る必要があるか?
21
細かな違い
if (example.isTrue) { ... } else { ... }
if(example.isTrue) { ... } else{ ... }
if (example.isTrue) { ... } else { ... }
22
タブ vs スペース
23
Emacs vs Vim
24
なぜコーディング規約を守る必要があるか?
複数人で開発する際に起きる好みの違いを吸収するため
↓コードの私物化を防ぐ
25
EditorConfigプロジェクトでのエディタの設定を共有できる
[*] charset = utf-8 end_of_line = lf trim_trailing_whitespace = true indent_style = space indent_size = 2
[*.js] indent_style = space indent_size = 2
[Makefile] indent_style = tab indent_size = 4
26
let s = a(p.h);
function a(b) { return b * 900 + 4000; }
let salary = getSalary(person.hours);
function getSalary(hours) { return hours * 900 + 4000; }
26
27
明確な名前をつける
28
関数やメソッドの命名パターンの一例
パターン プレフィックス 例
getter / setter get / set getText(), setText()
boolean is, can, has isEmpty()
外部から取得する fetch fetchImageTitle()
イベント発生時 on onItemClick()
29
明確な名前をつける
let salary = getSalary(person.hours);
function getSalary(hours) { return hours * 900 + 4000; }
let salary = computeSalary(person.workedHours);
function computeSalary(workedHours) { return workedHours * 900 + 4000; }
30
ロジックを確認しなくても把握しやすい
let salary = computeSalary(person.workedHours);
31
スコープと命名
スコープと変数の持つ意味を考える
void swap(int *a, int *b) { int tmp;
tmp = *a; *a = *b; *b = tmp; }
32
ゆとりを持つ
33
ゆとりを持つ
<?php /*認証�必要������読�込� */ require_once( 'twitter/' .'config.php'); require_once( 'twitter/' .'twitteroauth.php'); session_start();
// getToken.php ������ oauth_token �一致������� if ($_SESSION['oauth_token'] !== $_REQUEST['oauth_token']) { unset($_SESSION); echo '<a href="getToken.php">token不一致</a>'; exit; } // access token 取得 $tw = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $_SESSION['oauth_token'], $_SESSION['oauth_token_secret']); $access_token = $tw->getAccessToken($_REQUEST['oauth_verifier']); // Twitter � user_id + screen_name(表示名) $_SESSION['user_id'] = $access_token['user_id']; $_SESSION['screen_name'] = $access_token['screen_name'];
include('db_connection.php'); $sql = 'SELECT * FROM user_data WHERE user_id = ' . $_SESSION['user_id'$query = mysql_query($sql, $link);
34
適切な改行とスペース<?php // 認証�必要������読�込� require_once('twitter/' . 'config.php'); require_once('twitter/' . 'twitteroauth.php');
session_start();
// getToken.php ������ oauth_token �一致������� if ($_SESSION['oauth_token'] !== $_REQUEST['oauth_token']) { unset($_SESSION); echo '<a href="getToken.php">token不一致</a>'; exit; }
// access token 取得 $tw = new TwitterOAuth( CONSUMER_KEY, CONSUMER_SECRET, $_SESSION['oauth_token'], $_SESSION['oauth_token_secret'] );
$access_token = $tw -> getAccessToken($_REQUEST['oauth_verifier']);
35
マジックナンバー
36
マジックナンバーは敵
let salary = computeSalary(person.workedHours);
function computeSalary(workedHours) { return workedHours * 900 + 4000; }
37
なぜマジックナンバーがダメなのか?
一目で何を表しているのか分からない
変更があった際、すべての箇所を変更する必要がある
バグの原因に
↓定数に置き換え、一つにまとめる
38
マジックナンバーの置き換え
let salary = computeSalary(person.workedHours);
function computeSalary(workedHours) { return workedHours * 900 + 4000; }
let salary = computeSalary(person.workedHours);
function computeSalary(workedHours) { const BASE_SALARY = 4000; const HOURLY_PAYMENT = 900;
return workedHours * HOURLY_PAYMENT + BASE_SALARY; }
39
適切なコメントを書く
40
不要なコメント
// 引数�定数倍�返� function multiple(number) { const MULTIPLIER = 5;
return number * MULTIPLIER; }
41
不要なコメント (補助的なコメント)// ����楽曲�名前�取得�� function getMusicList() {
...
return musicList; }
function fetchAllMusicName() {
...
return musicNameList; }
42
適切なコメント (新たな情報を伝えている)// 10秒未満����������制限�掛������ const SLEEP_TIME = 10
43
3. 内面の改善
44
ユニットテストとリファクタリング
45
ユニットテスト (単体テスト)1つのモジュールのロジックに対して、
入力を与え、期待した結果が得られるかをテストするための手法
46
リファクタリング
外部から見た時の振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること
(新装版 リファクタリング より)
↓ユニットテストを活用することで振る舞いを保証する事ができる
47
ガード節の利用
48
{ "status": 0, "player_data": { "player_name": "Name", "total_score": "394262836", "rank": 3083, "average_score": "851539", } }
// GET ������取得�� JSON ��������列挙�� request.get(URL, function(error, response, body) { if (!error) { let json = JSON.parse(body);
if (json.status === 0) { let playerData = json["player_data"];
for (let p in playerData) { if (playerData[p] !== null) { console.log(p + " is " + playerData[p]); } } } } });
48
49
ガード節の利用
異常な場合は即 return する
if-else はどちらも正常な処理の場合に使う
50
// GET ������取得�� JSON ��������列挙�� request.get(URL, function(error, response, body) { if (error) { return; }
let json = JSON.parse(body);
if (json.status !== 0) { return; }
let playerData = json["player_data"];
for (let p in playerData) { if (playerData[p] === null) { continue; }
console.log(p + " is " + playerData[p]); } });
50
51
分かりづらい条件分岐の改善
52
例えば…if (serverA.status !== 0 && serverB.status !== 0) { // ���A�B���異常�時�処理 } else if (serverA.status !== 0 && serverB.status === 0) { // ���A�異常�時�処理 } else if (serverA.status === 0 && serverB.status !== 0) { // ���B�異常�時�処理 }
53
if (serverA.status !== 0) { if (serverB.status !== 0) { // ���A�B���異常�時�処理 } else { // ���A�異常�時�処理 } } else { if (serverB.status !== 0) { // ���B�異常�時�処理 } }
53
54
54
55
分かりづらい条件分岐
if (serverA.status !== 0 && serverB.status !== 0) { // ���A�B���異常 } else if (serverA.status !== 0) { // ���A�異常 } else if (serverB.status !== 0) { // ���B�異常 }
56
分かりづらい条件分岐の特徴
&& や || を多用している
否定の条件が多い
マジックナンバーとの比較
↓メソッドや関数に抽出する
57
分かりづらい条件分岐をメソッドで置き換える
// ����������� public boolean isStatusError() { if (status !== 0) { return false; }
return true;}
if (serverA.isStatusError() && serverB.isStatusError()) { // ���A�B���異常 } else if (serverA.isStatusError()) { // ���A�異常 } else if (serverB.isStatusError()) { // ���B�異常 }
58
神のクラス (God class)
59
神のクラス (God class)すべてを創造することのできるクラス
神のメソッド、神の関数、etc.
60
神の* を錬成しやすいポイント
一つのメソッド当たりの行数が多い
不適当なクラスにメンバ変数やメソッドを割り当てている
ユニットテストを書きにくい
単一責任原則を守っていない
一時変数が多い
etc.
61
単一責任原則
変更する理由が同じものは集める。変更する理由が違うものは分ける。(プログラマが知るべき97のこと より引用)
62
神の* を錬成しやすいポイント
一つのメソッド当たりの行数が多い
不適当なクラスにメンバ変数やメソッドを割り当てている
ユニットテストを書きにくい
単一責任原則を守っていない
一時変数が多い
etc.
63
一時変数が多い場合
ローカル変数である
↓
そのメソッドでしか利用できない
↓
神のメソッド化
↓
64
一時変数をメソッドや関数で置き換える
let exists = false;
for (let i = 0; i < BLACK_LIST.length; i++) { if (person.name === BLACK_LIST[i]) { exists = true; break; } }
if (exists) { // BAN�� } else { // 一般人�時�処理 }
65
メソッドに抽出する
function isExists(person.name) { let exists = false;
for (let i = 0; i < BLACK_LIST.length; i++) { if (person.name === BLACK_LIST[i]) { exists = true; break; } }
return exists; }
↓function isMarkedPerson(name) { for (let i = 0; i < BLACK_LIST.length; i++) { if (name === BLACK_LIST[i]) { return true; } }
return false; }
66
呼び出し側
if (isMarkedPerson(person.name)) { // BAN�� } else { // 一般人�時�処理 }
67
オブジェクトを利用したリファクタリング
( JavaScript → Java )
68
オブジェクト指向プログラミング
(OOP: Object-Oriented Programming)
69
手続き型プログラミング
関数を元に一連のステップを実行していく
70
オブジェクト指向プログラミング
オブジェクト間でメッセージをやりとりしながらプログラムを構成する手法
オブジェクトは状態(メンバ変数)と振る舞い(メソッド)を持つ
71
車
名前 例
クラス 設計図
メソッド 機能
メンバ 状態(速度)
インスタンス 実際の車
72
OOP の3大要素
継承
カプセル化
ポリモーフィズム
73
継承
抽象的なクラスの要素をより具体的なクラスへそのまま引き継ぐ
例: 車 → 軽自動車、パトカー、救急車、トラック、etc.
74
カプセル化
不必要なメンバ変数やメソッドを隠蔽する
例: エンジンの動作の隠蔽
75
ポリモーフィズム
子孫関係にあるクラスはその親クラスを元にして抽象的に扱うことができる
例: すべての車はアクセルとブレーキがある
76
メンバ変数を一つにまとめる
String firstName, lastName, email, nickname; int gender;
firstName = json.getString("firstName"); lastName = json.getString("lastName"); email = json.getString("email"); nickname = json.getString("nickname"); gender = Integer.parseInt(json.getString("gender"));
77
データクラス
class Profile { private String firstName, lastName, email, nickname; private Gender gender;
Profile(String firstName, String lastName, String email, String nickname, Gender gender) { this.firstName = firstName; this.lastName = lastName; this.email = email; this.nickname = nickname; this.gender = gender; }
// getter & setter }
78
呼び出し側
Profile profile = new Profile( json.getString("firstName"), json.getString("lastName"), json.getString("email"), json.getString("nickname"), Integer.parseInt(json.getString("gender")) );
profile.getFirstName();
79
null オブジェクトの利用
80
null オブジェクトの利用public class PartTimeStudent { private String firstName, ..., email, hireDate;
// 下��� ParensHome ����利用���� // 住民票�実家���場合�� private ParentsHome parentsHome;
// getter & setter }
class ParentsHome { private String phoneNumber; private String address; private String representative;
public String getPhoneNumber() { return phoneNumber; }
// getter & setter }
81
if や switch を多用せざるを得ないif (exampleStudent.getParentsHome() === null) { System.out.println("実家�電話番号: -"); }
String phoneNumber = exampleStudent.getParentsHome().getPhoneNumber();System.out.println("実家�電話番号: " + phoneNumber);
82
1. nullオブジェクトの追加
class NullParentsHome extends ParentsHome {
... }
83
2. isNull() の追加
class ParentsHome { private String phoneNumber; private String address; private String representative;
public boolean isNull() { return false; }
public String getPhoneNumber() { return this.phoneNumber; }
... }
class NullParentsHome extends ParentsHome { @Override public boolean isNull() { return true; }
... }
84
3. PartTimeStudent クラスの呼び出し元の修正
public class PartTimeStudent { ...
ParentsHome getParentsHome() { return parentsHome; }
... }
↓public class PartTimeStudent { ...
ParentsHome getParentsHome() { if (parentsHome === null) { return new NullParentsHome(); }
return parentsHome; }
... }
85
4. null 判定箇所を isNull() で置き換え、テストする
if (exampleStudent.getParentsHome() === null) { return; }
if (exampleStudent.getParentsHome().isNull()) { return; }
86
5. ParentsHome が null の時の getter の値を設ける
class NullParentsHome extends ParentsHome { @Override public boolean isNull() { return true; }
@Override public String getPhoneNumber() { return "-"; }
... }
87
6. if を削除できるif (exampleStudent.getParentsHome() === null) { return; }
String phoneNumber = exampleStudent.getParentsHome().getPhoneNumber();System.out.println("実家�電話番号: " + phoneNumber);
↓String phoneNumber = exampleStudent.getParentsHome().getPhoneNumber();System.out.println("実家�電話番号: " + phoneNumber);
出力
実家�電話番号: 0000-11-2222
実家�電話番号: -
88
4. まとめ
89
意識をしてコードを書く
常にシンプルを心がける
コードを知らない人への配慮
より良い書き方
違和感を感じ取る
90
より良いコードを書くための近道
91
スパゲッティを作ってみる
92
スパゲッティを作ってみる
どこが原因なのか?
どうすれば改善できるのか?
一つずつ対処していく
↓楽しくなってくる!
93
楽しくコードを書こう!!
95
参考書籍
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック
JUnit実践入門 ~体系的に学ぶユニットテストの技法
新装版 リファクタリング―既存のコードを安全に改善する―
97
出典 (スライド内で使用した画像)
ヴォイニッチ手稿:
青峰: アニメ黒子のバスケ2期OP
TABS vs SPACES:
Emacs vs Vim:
どうしてこうなった: 2ちゃんねるAA
FXで有り金全部溶かした人の顔: あいまいみー第9話
やらなければ、一生わからん!!: 燃えよペン
Voynich Manuscript
Truly Code
4.bp.blogspot.com