タグ別アーカイブ: coding

『リーダブルコード』を読みました。(3)

コードのスタイル、見た目

こんにちは、ニタです。
リーダブルコード第3回目は、「コードのスタイル、見た目」について書きたいと思います。

可読性が高く、読みやすいコードを書くには、分かりやすい変数、関数の命名規則の他に、見た目も意外と大事だと思います。

どんなにバグがなく高速に処理できるプログラムでも、ファイルを開いてみると、コードがゴチャゴチャしていて、理解するのに時間が掛かるようでは、修正する際困るのはコードを書いた自分であり、修正することになる自分を含む誰かです。

コードがキレイだと、さっと流し読みができて理解しやすく、修正、リファクタリングのしやすさに繋がると思います。
では、キレイなコードを書くためには何が重要なのでしょうか。

キレイなコードを書くためのポイント

キレイなコードを書くために気をつけるべきポイント・原則は、この3点だと思います。

  • 読み手が慣れているパターン、一貫性のあるレイアウトを使う
  • 似ているコードは似ているように見せる
  • 関連するコードは1ブロックにまとめる

これらを実施するためには、実際にコードを書く段階でどのような事を行えばいいでしょうか。

一貫性のある簡潔な改行位置

例えば、関数の引数の指定など、コードの改行位置を統一することでコードを見やすくなる場合もあります。

以下は、投稿内容を取得する関数で、ニュースとブログの一覧を取得するコードです。
Post::getList()で必要な引数ごとに改行し、各引数の横にそれぞれの引数が何を意味するのかをコメントで書いてみました。

$news_list = Post::getList(
    'news', // 投稿タイプ
    20, // 1ページあたりの表示件数
    1, // 表示ページ
    true // 降順表示
);

$blog_list = Post::getList(
    'blog', // 投稿タイプ
    10, // 1ページあたりの表示件数
    1, // 表示ページ
    false // 降順表示
);

vimなどで、複数のファイルを同時に表示した際、1行あたりの文字数が限られてしまいます。
そのため、このように縦に長いコードの方が読みやすい場合も出てくると思います。
また、直接引数に値を入力せずに、分かりやすい変数名を決め、変数の上部や後ろにコメントで説明を追記しても良いですが、変数を作るまでもない場合はこのように書くのもアリかと思います。

ただ、上記のように同じメソッドを複数回呼び出す場合だと、縦に長くなるし、同じコメントを何回も書かなければなりません。
書き手も読み手も、ちょっと疲れますよね。

では、どう書けば良いか。
最初のメソッドだけコメントを書いて、以降は書かないというのも、コードのスタイルが統一されておらず、おかしいです。
コメントが書かれているメソッドが削除され、書かれていないメソッドだけが残ったら、途端に引数の内容が分からなくなるでしょう。
また、前述した「似ているコードは似ているように見せる」というポイントからも外れてきます。

このような場合は、下記のように仮の引数を使い、説明用のコメントを用意すると分かりやすくなると思います。

// Post::getList($post_type, $posts_per_page, $page, $is_desc_sort);
//               投稿形式, 1ページあたりの表示件数, 表示ページ, 降順表示するか

$news_list = Post::getList('news',20,1,true);
$blog_list = Post::getList('blog',10,1,false);

メソッドを使った整列、整頓

どんどんコードを書いていくうちに、コードが長く複雑になり、読みにくくなっていく場合もあるでしょう。
その場合、別のメソッドに分けることで、コードを簡潔になるケースもあります。
そうすることで、1つのコードの中で似たような処理を何度も書かなくてはいけない部分を、メソッドの呼び出しだけで済むようになります。

それにより、以下の様な副作用が生じる場合があります。

  • コードが簡潔になり、可読性が上がる
  • メソッドの呼び出しが簡潔になり、テスト用コードの追加が簡単になる

縦の線をまっすぐにする

縦の線、縦の並びを整えるということです。
ちょっと面倒ですが、こんな感じに並びをキレイにすると流し読みしやすくなります。

$detail = $request->post('detail');
$name   = $request->post('name');
$url    = $equest->post('url');
$phone  = $request->post('phone');
$email  = $request->post('email');

こういう感じで。

と、終わりたいところですが、上のコードの中でタイプミスがあります。
どこだか分かりますか?

縦の並びを意識して整列させると、こういったミスを見つけやすくなると思います。
($urlの右辺、$requestが誤字)
でも、手間っちゃ手間です。時間に余裕がある時にやる感じの作業に感じます。
だからやらなくても良いかなとも思います。とは言え、流し読みはしやすくなるなとも感じるので、一概にやらない訳にもいかない作業です。
なので、時間的余裕と、今後そのコードを読む人への親切心がある場合に行う程度の作業と捉えておいてください。

一貫性と意味のある並び

「似ているコードは似ているように見せる」というポイントを実施するための書き方になります。

Webサイトのフォームのコーディングで、inputタグなど同じコードが複数ある場合、タグ内の属性の順番を統一させるなど、統一感を持たせることで、可読性が上がります。

<input type="text" name="name" value=""/>
<input type="text" name="email" value=""/>
<input type="text" name="tel" value=""/>
<input type="text" name="address" value=""/>

タグごとに属性の順番がバラバラだと、コードを読み進めていくうちに一貫性のなさにイライラしたり、逆に意味があるのではないかと疑問に思ったりするかもしれません。

また、そのフォームでの入力内容を制御する側のコードでも、inputタグが書かれた順番に処理していくと、統一感が生まれ理解しやすいコードになると思います。

// 処理する順番を、inputタグの順番に合わせる。
$name = $_POST['name'];
$email = $_POST['email'];
$tel = $_POST['tel'];
$address = $_POST['address'];

inputタグ順など基準となるものがない場合は、重要な項目順、アルファベット順など、ある一定の順番を決めておくといいと思います。

宣言をブロックにまとめる

コードを段落ごとに分割して読みやすくする、ということです。
ハンドラ系ならそれらで一塊に、ヘルパー系ならそれだけの塊でまとめます。
更に、先頭部分にコメントで何のブロックなのか、どういった処理を行うのかなど説明を書いておくと、より分かりやすくなるでしょう。

WordPressのテーマファイルの土台となるファイル群を自動生成する「_s」(underscores)というサービスがあります。

_s

作成したいテーマ名を入力すると、ヘッダー、フッター、サイドバーや基本的なCSSファイルなどを生成してくれます。
その中のstyle.cssには、ファイル内でスタイル定義する内容の一覧表がコメントで書かれています。

更に項目ごと(ヘッダー部分、サイドバー部分)のスタイル定義を開始する前にコメントで、何のスタイル定義なのかを明記しており、手を加えやすい構成になっています。

個人的な好みと一貫性

最後に、個人的な好みに近いことです。
プロジェクト、チームごとの決まり事として、コーディング規約を決めておくと良いよという話です。
ifの「{」をifと一緒の行にするか、1行にするかとか、そういう話ですね。

if($hoge){
}
//もしくは
if($hoge)
{
}

コーディング規約は、個人的な好みで決める前に、使用するプログラム言語にも左右されるので、そこは注意しておく必要があります。

また、プロジェクトや社内ルールなどで決まったコーディング規約があるなら、それに合わせた方が良いでしょう。

規約が曖昧だったり、統一されていないと、コードが読みにくくなります。
プログラム言語、フレームワークによって、規約は様々あります。

まとめ

いつも書いていくと長くなるのですが、まとめてしまうとこんなに簡潔なことになります。

  • 複数のコードブロックで、同じようなコードがあれば、シルエット(見た目)も同じようにすべき。
  • コードの「列」を整列すれば、概要が把握しやすい。
  • 意味ある順番を選び、常に守る。(例:inputの要素の並び)
  • 空行を使い、大きなブロックを論理的な「段落」にする。
  • コーディング規約などプロジェクト、チームごとの規約に準拠した書き方をする。

まずは仕様通りに、期待した動きができるようコードを書き進めるのが第一ですが、書き進めていくなかで、これまで書いてきたコードを振り返ったりする場合もあると思います。
その際に、ゴチャゴチャしすぎて分からなくなるようでは、どんなに動くコードでも、理解できなければ、修正できなければ、いざという時困ったことになります。

そのような事を防ぐためにも、日頃から少しずつ、今回のコードの整理を行うことで、読みやすいコードに近づくことができると思います。
それでは、また。


『リーダブルコード』を読みました。(1)

こんにちは、ニタです。

最近、オライリーの『リーダブルコード』という本を読みました。
その名の通り、読みやすいコードを書くためのノウハウが書かれた、大変ためになる内容でした。

どれだけ短期間でプログラムが出来たとしても、後で他の人が修正を加える事になった時、どこで何の機能をどのように書いているのか、全く理解できなければ、メンテナンス性の低いコードになってしまいます。
最初にプログラムを書いた人が、ずっと保守していかなければならないのでしょうか。
その人が設計内容を忘れてしまったら、どう保守していけばいいのでしょう。

様々な開発言語や、日々新しく出てくるシステムに触れてみるのも大事ですが、プログラムを書くためには、この本に書かれてあるような、もっと基礎的なことが大事だと思います。
これから数回に分けて、この本について感じたこと、大事だと思ったことを書いていきたいと思います。
よろしくお願い致します。

理解しやすいコードとは

僕がプログラマという肩書を戴き、プログラミングの仕事をもらうようになってから、自分で書いたものはともかく、様々なフレームワーク、他のプログラマの方々のコード…いろんなコードを目にしてきました。
共通して分かっていることは、「読みやすいコードは分かりやすい」ということです。
まあ、当然なことなんですけどね…。 そう、コードは理解しやすい内容じゃないといけません。
じゃあ「理解しやすい」って何でしょうか。

コードの意味が分かるということではないでしょうか。
他の人(プロジェクトに参加していない同僚や新しく参加する人)や未来の自分が、そのコードを読んで、短時間で変更を加える事が出来たり、バグを見つけることができることが「理解できる」ということではないでしょうか。
自分が書いたコードの内容って、時間が経つと忘れたりしますしね…。

「簡潔」と「安心」どちらが大事なのでしょう

また、簡潔なコードは理解しやすいと感じる場合もあるでしょう。コード数、行数が少なければその分コードを読む時間は短縮されますし。
では、この場合はどうでしょうか。

return ($foo > 0) ? $hoge * (1 * $var) : $hoge / (1 * $var);

ワンライナーで書かれていて、スマートって感じです。
これをこう書くと、どう感じるでしょうか。

if($foo > 0){
return $hoge * (1 * $var);
} else {
return $hoge / (1 * $var);
}

前者のほうが簡潔なコードですが、読んだときパッと理解できたのは後者のほうではないでしょうか。
後者の方が読んでいて分かりやすく安心します。
簡潔なコードも大事ですが、読んだときの「安心さ」も、分かりやすいコードとして大事なのではないでしょうか。

そのコード、後で読んで理解できますか?

ところで、自分が書いたコードを「理解しやすいコード」と思って書いていますか?
例えそう思っていたとしても、それは自分が仕様を理解し、設計しているからだけではないでしょうか。

前述しましたが、そのプロジェクトに関係のない同僚、もしくはそのプロジェクトに新しく入った人が、そのコードを読んで理解できる内容かどうかが大事だと思うのです。

新しくコードを書くとき、修正するとき、すぐに手を動かそうとせず、
「これから書くコードは理解しやすいコードだろうか?」と一歩引いてみることも大事だと思います。

まとめ

以上のことから、理解しやすいコードを書くためのポイントは、このようなことではないでしょうか。

  • コードは(未来の自分も含む)他人が、最短時間で理解できるように書かなければならない。
  • コードは短くした方がいい。でも「理解するまでにかかる時間」を短くする方が優先。
  • コードを書くとき、すぐにコードの修正に入らず、「このコードは理解しやすいだろうか?」と一歩引いて自問自答し、考えることから始める。

次回は、変数名、関数名などの名前について触れてみたいと思います。
それでは、また。


大阪の居酒屋ランチのバイキングルールをPythonで実装してみた

タグ: , | 投稿日: 投稿者:

こんにちはベッチです。

昨日出張で大阪に行ってきました。

大阪っぽい食事をしたかったのですが事前調査など全くしていなかった結果、大阪駅近くにある「新梅田食堂街」という所にある「梅田木曽路」という居酒屋ランチを食べる事にしました。

梅田木曽路

バイキング形式で550円でした。

安い。

ただし、安いバイキングという事で独自のルールが課せられます。

以下がそのルールです。

木曽路バイキングルール

いやー、ルールがやかましいですね!

でもコストパフォーマンス優れてる事考えれば特に気になりません。

ただし、ルール守らないとちょっとこわいおばちゃんに叱られます。

気になる方は是非行ってみて下さい。

という前置きはさておき、この梅田木曽路のバイキングルールをPythonで実装してみました。

module.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

MAX_COUNT = 1 # おかわりは1回まで

AMOUNT_SMALL = '少なめ'
AMOUNT_REGULAR = '普通'

FOOD_RICE = 'ごはん'
FOOD_MISO_SOUP = 'みそ汁'
FOOD_SIDE_DISH = 'おかず'
FOOD_FRUITS = 'フルーツ'
FOOD_OMELET = '玉子焼'

class Food:

	def __init__(self, name, amount, human):
		self._name = name # 名前(ごはん、みそ汁、おかず、フルーツ、玉子焼)
		self._count = 0 # おかわり回数
		self._amount = amount # 量
		self._first_amount = amount # 量
		self._human = human # 食べる人

	def get_name(self):
		return self._name
	def set_name(self, name):
		self._name = name
	def del_name(self):
		del self._name
	name = property(get_name, set_name, del_name)

	def get_count(self):
		return self._count
	def set_count(self, count):
		self._count = count
	def del_count(self):
		del self._count
	count = property(get_count, set_count, del_count)

	def get_amount(self):
		return self._amount
	def set_amount(self, amount):
		self._amount = amount
	def del_amount(self):
		del self._amount
	amount = property(get_amount, set_amount, del_amount)

	def get_first_amount(self):
		return self._first_amount
	def set_first_amount(self, first_amount):
		self._first_amount = first_amount
	def del_first_amount(self):
		del self._first_amount
	first_amount = property(get_first_amount, set_first_amount, del_first_amount)

	def get_human(self):
		return self._human
	def set_human(self, human):
		self._human = human
	def del_human(self):
		del self._human
	human = property(get_human, set_human, del_human)

class Human:

	def __init__(self, name):
		self._name = name # 名前
		self._lunch = []

	def get_name(self):
		return self._name
	def set_name(self, name):
		self._name = name
	def del_name(self):
		del self._name
	name = property(get_name, set_name, del_name)

	def get_lunch(self):
		return self._lunch
	def set_lunch(self, lunch):
		self._lunch = lunch
	def del_lunch(self):
		del self._lunch
	lunch = property(get_lunch, set_lunch, del_lunch)

	# 最初の準備をする
	def ready(self, name, amount):
		food = Food(name, amount, self)
		self.lunch.append(food)
		print name + 'を準備しました'
		return food

	# 食べる
	def eat(self, food):
		print food.name + 'を食べます'
		if food.human != self:
			print '[店員]自分のものは自分で食べなさいよ!'
			return

		if food.amount is None:
			print '[店員]' + food.name + '食べたければ自分で準備しなさいよ!'

		print food.name + 'を食べました'
		food.amount = None

	# おかわり
	def get_seconds(self, food, amount):
		food.count = food.count + 1
		print food.name + 'のおかわりをします'

		for my_food in self.lunch:
			if my_food.name is food.name:
				if my_food != food:
					print '[店員]器は最初に用意したものをつかってくれ!'
					return

		if food.name == FOOD_OMELET:
			print '[店員]' + food.name + 'はおかわりできないよ!'
			return

		if food.name == FOOD_SIDE_DISH:
			for my_food in self.lunch:
				if my_food.name == FOOD_RICE and my_food.first_amount == AMOUNT_SMALL:
					print '[店員]最初のごはんの量が少ないからおかずのおかわりはできないよ!'
					return

		if food.count > MAX_COUNT:
			print '[店員]' + food.name + 'のおかわりは既に一回してるだろ!'
			return

		if not food.amount is None:
			print '[店員]全部食べきってからじゃないとおかわりできないよ!'
			return

		food.amount = amount
		print food.name + 'のおかわりをしました'
		self.eat(food)

	# 帰る
	def go_back(self):
		for my_food in self.lunch:
			if not my_food.amount is None:
				print '[店員]' + my_food.name + 'がまだ残っているわよ!'
				return
		print 'ごちそうさまでした'

kisojiLunch.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

if __name__ == "__main__":

	import module

	betchi = module.Human('betchi')

	rice = betchi.ready(module.FOOD_RICE, module.AMOUNT_SMALL);
	miso_soup = betchi.ready(module.FOOD_MISO_SOUP, module.AMOUNT_REGULAR);
	side_dish = betchi.ready(module.FOOD_SIDE_DISH, module.AMOUNT_REGULAR);
	omelet = betchi.ready(module.FOOD_OMELET, module.AMOUNT_REGULAR);

	betchi.eat(rice)
	betchi.get_seconds(rice, module.AMOUNT_REGULAR)
	betchi.eat(miso_soup)
	betchi.eat(side_dish)
	betchi.get_seconds(side_dish, module.AMOUNT_REGULAR)

	betchi.eat(omelet)
	betchi.get_seconds(omelet, module.AMOUNT_REGULAR)

	betchi.go_back()

pythonが実行できる環境で「python kisojiLunch.py」ってやってもらえれば実行できます。

実行結果はこんな感じです。

ごはんを準備しました
みそ汁を準備しました
おかずを準備しました
玉子焼を準備しました
ごはんを食べます
ごはんを食べました
ごはんのおかわりをします
ごはんのおかわりをしました
ごはんを食べます
ごはんを食べました
みそ汁を食べます
みそ汁を食べました
おかずを食べます
おかずを食べました
おかずのおかわりをします
[店員]最初のごはんの量が少ないからおかずのおかわりはできないよ!
玉子焼を食べます
玉子焼を食べました
玉子焼のおかわりをします
[店員]玉子焼はおかわりできないよ!
ごちそうさまでした

みなさんもたまには身の回りのものを実装してみてはいかがでしょうか。

それでは良い週末を。