97
1 For writing clean code

わかるコードを書くために For writing clean code

Embed Size (px)

Citation preview

Page 1: わかるコードを書くために For writing clean code

1

わかるコードを書くために

For writing clean code

Page 2: わかるコードを書くために For writing clean code

2

$ whoami

青木 太郎

Eyes, JAPAN / ヅ大 2年目

Groove Coaster

STEINS;GATE

色々...

Page 3: わかるコードを書くために For writing clean code

3

わからないコードs = a(p.h);

function a(b) { return b * 900 + 4000; }

Page 4: わかるコードを書くために For writing clean code

4

Page 5: わかるコードを書くために For writing clean code

4

5

目次

1. 目的

2. 表面の改善

3. 内面の改善

4. まとめ

Page 6: わかるコードを書くために For writing clean code

6

1. 目的

Page 7: わかるコードを書くために For writing clean code

7

主な対象者

一人でコードを数千行書いたことのない人

Page 8: わかるコードを書くために For writing clean code

8

プログラミング

コードを読む

コードを書く

Page 9: わかるコードを書くために For writing clean code

9

コードを読む時間のほうが書く時間より圧倒的に多い

プロジェクトのコードの処理の把握

バグの発生場所の検証

ライブラリなどのドキュメント

Page 10: わかるコードを書くために For writing clean code

10

コードが汚いと…

誰も読めなくなる

保守性が下がる

バグが混入しやすくなる

楽しくない

Page 11: わかるコードを書くために For writing clean code

11

きれいなコードを書けると…

自然と誰が見ても分かりやすいコードになる

嬉しくなってテンションが上がり、ゾーンに突入できる

Page 12: わかるコードを書くために For writing clean code

12

一人で作業するならきれいなコードを書く必要はない?

Page 13: わかるコードを書くために For writing clean code

13

一人で作業するならきれいなコードを書く必要はない?

数週間後の自分は他人

Page 14: わかるコードを書くために For writing clean code

14

数週間後の自分は他人

Page 15: わかるコードを書くために For writing clean code

15

2. 表面の改善

Page 16: わかるコードを書くために For writing clean code

16

コーディング規約殆どの言語でコーディング規約が存在する

Page 17: わかるコードを書くために For writing clean code

17

Page 18: わかるコードを書くために For writing clean code

17

18

代表的な表記法

名前 例

アッパーキャメルケース(UCC) ExampleString

ローワーキャメルケース(LCC) exampleString

スネークケース example_string

ハンガリアン記法 mExampleString

Page 19: わかるコードを書くために For writing clean code

19

わりと一般的な命名規則の例

名前 表記 例

クラス UCC class ImageUtil {...}

変数、メンバ変数 LCC or スネークケース let imageTitle;

関数、メソッド LCC function fetchImageTitle() {...}

定数 大文字 + スネークケース const MAX_STRING;

Page 20: わかるコードを書くために For writing clean code

20

なぜコーディング規約を守る必要があるか?

Page 21: わかるコードを書くために For writing clean code

21

細かな違い

if (example.isTrue) { ... } else { ... }

if(example.isTrue) { ... } else{ ... }

if (example.isTrue) { ... } else { ... }

Page 22: わかるコードを書くために For writing clean code

22

タブ vs スペース

Page 23: わかるコードを書くために For writing clean code

23

Emacs vs Vim

Page 24: わかるコードを書くために For writing clean code

24

なぜコーディング規約を守る必要があるか?

複数人で開発する際に起きる好みの違いを吸収するため

↓コードの私物化を防ぐ

Page 25: わかるコードを書くために For writing clean code

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

Page 26: わかるコードを書くために For writing clean code

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; }

Page 27: わかるコードを書くために For writing clean code

26

27

明確な名前をつける

Page 28: わかるコードを書くために For writing clean code

28

関数やメソッドの命名パターンの一例

パターン プレフィックス 例

getter / setter get / set getText(), setText()

boolean is, can, has isEmpty()

外部から取得する fetch fetchImageTitle()

イベント発生時 on onItemClick()

Page 29: わかるコードを書くために For writing clean code

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; }

Page 30: わかるコードを書くために For writing clean code

30

ロジックを確認しなくても把握しやすい

let salary = computeSalary(person.workedHours);

Page 31: わかるコードを書くために For writing clean code

31

スコープと命名

スコープと変数の持つ意味を考える

void swap(int *a, int *b) { int tmp;

tmp = *a; *a = *b; *b = tmp; }

Page 32: わかるコードを書くために For writing clean code

32

ゆとりを持つ

Page 33: わかるコードを書くために For writing clean code

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);

Page 34: わかるコードを書くために For writing clean code

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']);

Page 35: わかるコードを書くために For writing clean code

35

マジックナンバー

Page 36: わかるコードを書くために For writing clean code

36

マジックナンバーは敵

let salary = computeSalary(person.workedHours);

function computeSalary(workedHours) { return workedHours * 900 + 4000; }

Page 37: わかるコードを書くために For writing clean code

37

なぜマジックナンバーがダメなのか?

一目で何を表しているのか分からない

変更があった際、すべての箇所を変更する必要がある

バグの原因に

↓定数に置き換え、一つにまとめる

Page 38: わかるコードを書くために For writing clean code

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; }

Page 39: わかるコードを書くために For writing clean code

39

適切なコメントを書く

Page 40: わかるコードを書くために For writing clean code

40

不要なコメント

// 引数�定数倍�返� function multiple(number) { const MULTIPLIER = 5;

return number * MULTIPLIER; }

Page 41: わかるコードを書くために For writing clean code

41

不要なコメント (補助的なコメント)// ����楽曲�名前�取得�� function getMusicList() {

...

return musicList; }

function fetchAllMusicName() {

...

return musicNameList; }

Page 42: わかるコードを書くために For writing clean code

42

適切なコメント (新たな情報を伝えている)// 10秒未満����������制限�掛������ const SLEEP_TIME = 10

Page 43: わかるコードを書くために For writing clean code

43

3. 内面の改善

Page 44: わかるコードを書くために For writing clean code

44

ユニットテストとリファクタリング

Page 45: わかるコードを書くために For writing clean code

45

ユニットテスト (単体テスト)1つのモジュールのロジックに対して、

入力を与え、期待した結果が得られるかをテストするための手法

Page 46: わかるコードを書くために For writing clean code

46

リファクタリング

外部から見た時の振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること

(新装版 リファクタリング より)

↓ユニットテストを活用することで振る舞いを保証する事ができる

Page 47: わかるコードを書くために For writing clean code

47

ガード節の利用

Page 48: わかるコードを書くために For writing clean code

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]); } } } } });

Page 49: わかるコードを書くために For writing clean code

48

49

ガード節の利用

異常な場合は即 return する

if-else はどちらも正常な処理の場合に使う

Page 50: わかるコードを書くために For writing clean code

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]); } });

Page 51: わかるコードを書くために For writing clean code

50

51

分かりづらい条件分岐の改善

Page 52: わかるコードを書くために For writing clean code

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�異常�時�処理 }

Page 53: わかるコードを書くために For writing clean code

53

if (serverA.status !== 0) { if (serverB.status !== 0) { // ���A�B���異常�時�処理 } else { // ���A�異常�時�処理 } } else { if (serverB.status !== 0) { // ���B�異常�時�処理 } }

Page 54: わかるコードを書くために For writing clean code

53

54

Page 55: わかるコードを書くために For writing clean code

54

55

分かりづらい条件分岐

if (serverA.status !== 0 && serverB.status !== 0) { // ���A�B���異常 } else if (serverA.status !== 0) { // ���A�異常 } else if (serverB.status !== 0) { // ���B�異常 }

Page 56: わかるコードを書くために For writing clean code

56

分かりづらい条件分岐の特徴

&& や || を多用している

否定の条件が多い

マジックナンバーとの比較

↓メソッドや関数に抽出する

Page 57: わかるコードを書くために For writing clean code

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�異常 }

Page 58: わかるコードを書くために For writing clean code

58

神のクラス (God class)

Page 59: わかるコードを書くために For writing clean code

59

神のクラス (God class)すべてを創造することのできるクラス

神のメソッド、神の関数、etc.

Page 60: わかるコードを書くために For writing clean code

60

神の* を錬成しやすいポイント

一つのメソッド当たりの行数が多い

不適当なクラスにメンバ変数やメソッドを割り当てている

ユニットテストを書きにくい

単一責任原則を守っていない

一時変数が多い

etc.

Page 61: わかるコードを書くために For writing clean code

61

単一責任原則

変更する理由が同じものは集める。変更する理由が違うものは分ける。(プログラマが知るべき97のこと より引用)

Page 62: わかるコードを書くために For writing clean code

62

神の* を錬成しやすいポイント

一つのメソッド当たりの行数が多い

不適当なクラスにメンバ変数やメソッドを割り当てている

ユニットテストを書きにくい

単一責任原則を守っていない

一時変数が多い

etc.

Page 63: わかるコードを書くために For writing clean code

63

一時変数が多い場合

ローカル変数である

そのメソッドでしか利用できない

神のメソッド化

Page 64: わかるコードを書くために For writing clean code

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 { // 一般人�時�処理 }

Page 65: わかるコードを書くために For writing clean code

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; }

Page 66: わかるコードを書くために For writing clean code

66

呼び出し側

if (isMarkedPerson(person.name)) { // BAN�� } else { // 一般人�時�処理 }

Page 67: わかるコードを書くために For writing clean code

67

オブジェクトを利用したリファクタリング

( JavaScript → Java )

Page 68: わかるコードを書くために For writing clean code

68

オブジェクト指向プログラミング

(OOP: Object-Oriented Programming)

Page 69: わかるコードを書くために For writing clean code

69

手続き型プログラミング

関数を元に一連のステップを実行していく

Page 70: わかるコードを書くために For writing clean code

70

オブジェクト指向プログラミング

オブジェクト間でメッセージをやりとりしながらプログラムを構成する手法

オブジェクトは状態(メンバ変数)と振る舞い(メソッド)を持つ

Page 71: わかるコードを書くために For writing clean code

71

名前 例

クラス 設計図

メソッド 機能

メンバ 状態(速度)

インスタンス 実際の車

Page 72: わかるコードを書くために For writing clean code

72

OOP の3大要素

継承

カプセル化

ポリモーフィズム

Page 73: わかるコードを書くために For writing clean code

73

継承

抽象的なクラスの要素をより具体的なクラスへそのまま引き継ぐ

例: 車 → 軽自動車、パトカー、救急車、トラック、etc.

Page 74: わかるコードを書くために For writing clean code

74

カプセル化

不必要なメンバ変数やメソッドを隠蔽する

例: エンジンの動作の隠蔽

Page 75: わかるコードを書くために For writing clean code

75

ポリモーフィズム

子孫関係にあるクラスはその親クラスを元にして抽象的に扱うことができる

例: すべての車はアクセルとブレーキがある

Page 76: わかるコードを書くために For writing clean code

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"));

Page 77: わかるコードを書くために For writing clean code

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 }

Page 78: わかるコードを書くために For writing clean code

78

呼び出し側

Profile profile = new Profile( json.getString("firstName"), json.getString("lastName"), json.getString("email"), json.getString("nickname"), Integer.parseInt(json.getString("gender")) );

profile.getFirstName();

Page 79: わかるコードを書くために For writing clean code

79

null オブジェクトの利用

Page 80: わかるコードを書くために For writing clean code

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 }

Page 81: わかるコードを書くために For writing clean code

81

if や switch を多用せざるを得ないif (exampleStudent.getParentsHome() === null) { System.out.println("実家�電話番号: -"); }

String phoneNumber = exampleStudent.getParentsHome().getPhoneNumber();System.out.println("実家�電話番号: " + phoneNumber);

Page 82: わかるコードを書くために For writing clean code

82

1. nullオブジェクトの追加

class NullParentsHome extends ParentsHome {

... }

Page 83: わかるコードを書くために For writing clean code

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; }

... }

Page 84: わかるコードを書くために For writing clean code

84

3. PartTimeStudent クラスの呼び出し元の修正

public class PartTimeStudent { ...

ParentsHome getParentsHome() { return parentsHome; }

... }

↓public class PartTimeStudent { ...

ParentsHome getParentsHome() { if (parentsHome === null) { return new NullParentsHome(); }

return parentsHome; }

... }

Page 85: わかるコードを書くために For writing clean code

85

4. null 判定箇所を isNull() で置き換え、テストする

if (exampleStudent.getParentsHome() === null) { return; }

if (exampleStudent.getParentsHome().isNull()) { return; }

Page 86: わかるコードを書くために For writing clean code

86

5. ParentsHome が null の時の getter の値を設ける

class NullParentsHome extends ParentsHome { @Override public boolean isNull() { return true; }

@Override public String getPhoneNumber() { return "-"; }

... }

Page 87: わかるコードを書くために For writing clean code

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

実家�電話番号: -

Page 88: わかるコードを書くために For writing clean code

88

4. まとめ

Page 89: わかるコードを書くために For writing clean code

89

意識をしてコードを書く

常にシンプルを心がける

コードを知らない人への配慮

より良い書き方

違和感を感じ取る

Page 90: わかるコードを書くために For writing clean code

90

より良いコードを書くための近道

Page 91: わかるコードを書くために For writing clean code

91

スパゲッティを作ってみる

Page 92: わかるコードを書くために For writing clean code

92

スパゲッティを作ってみる

どこが原因なのか?

どうすれば改善できるのか?

一つずつ対処していく

↓楽しくなってくる!

Page 93: わかるコードを書くために For writing clean code

93

楽しくコードを書こう!!

Page 94: わかるコードを書くために For writing clean code

94

スライド (Reveal.js版)

http://lycoris0731.github.io/slides

Page 95: わかるコードを書くために For writing clean code

95

参考書籍

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック

JUnit実践入門 ~体系的に学ぶユニットテストの技法

新装版 リファクタリング―既存のコードを安全に改善する―

Page 96: わかるコードを書くために For writing clean code

96

参考サイト

プログラマが知るべき97のこと

Page 97: わかるコードを書くために For writing clean code

97

出典 (スライド内で使用した画像)

ヴォイニッチ手稿:

青峰: アニメ黒子のバスケ2期OP

TABS vs SPACES:

Emacs vs Vim:

どうしてこうなった: 2ちゃんねるAA

FXで有り金全部溶かした人の顔: あいまいみー第9話

やらなければ、一生わからん!!: 燃えよペン

Voynich Manuscript

Truly Code

4.bp.blogspot.com