より良いプログラミングの入門
目次
より良いプログラミングの入門
私は業務でプログラム制作をお願いして作ってもらうことも多々あります。
その中で、レビュー時にこれはとても良いと感動することもあれば、正直お金を払いたくないと思うこともあります。 どういったプログラムがより良いと感じてもらえるのか、まとめたいと思います。
特に記載がない場合、 javascript
の記法で説明しますが、普遍的な事項となります。
よいプログラムとは
- ドキュメントが記載されている
- リファレンスを記録し、正しい参照が出来ている
- コーディング規約に従っている
- プログラム内のロジック粒度が揃っている
- 可能な限り
const
宣言がされている - 目的にあった機能を選択する事ができる
具体的な内容
ドキュメントが記載されている
ドキュメントの記載は、書いてあれば『よい』、というより『最低条件』だと考えています。
ドキュメントの記載がない場合。
- 一連のロジック自体の説明もなく、逐一説明を求めないと何をしたいのか分からない
- 説明を聞いても、本人の中で要件が理解出来ておらず、説明すらままならない
- 要件と全く異なる実装になっている という状況になります。
ドキュメントが無いプログラムは、レビューが出来ないような状態であるものが多いです。
ドキュメントは、要件の整理、必要な機能の認識、意識合わせのツールとして絶対に必要なものです。
ドキュメントを残す方法は Google Slide でも、 README でも、Doc Comment でも良いと考えていますが、 最低限、Function block の説明と、 一連のブロック単位で説明は入れるようにしてください。
作業者によっては、本当に下記のような状態でレビューを依頼してきます。
const logic = (num) => {
if ( num % 3 == 0 && num % 4 == 0 ) {
return "FizzBuzz"
}
else if ( num % 3 == 0 ) {
return "Fizz"
}
else if ( num % 4 == 0 ) {
return "Buzz"
}
else {
return num
}
}
正直、レビューのしようが無いと分かっていただければ嬉しいです。
/**
* FizzBuzz を計算します。
* see also: https://ja.wikipedia.org/wiki/Fizz_Buzz
*
* @param num {number} 計算する値
* @returns {number|string} Fizz、Buzz、FizzBuzz または数値
*/
const logic = (num) => {
// num が 3 と 4 で割り切れる場合
// 先に計算が必要
if ( num % 3 == 0 && num % 4 == 0 ) {
return "FizzBuzz"
}
// num が 3 で割り切れる場合
else if ( num % 3 == 0 ) {
return "Fizz"
}
// num が 4 で割り切れる場合
else if ( num % 4 == 0 ) {
return "Buzz"
}
// その他
else {
return num
}
}
ドキュメントがあるだけで、格段にレビューがしやすくなります。 また、コメントがあることで作成者が Buzz を誤って認識していることが分かります。
リファレンスを記録し、正しい参照が出来ている
ライブラリは、率先的に利用するべきだと考えています。 しかし、リファレンスが無いものや、そもそも正しくない参照をしているものなど多いです。
- 何を参照して作成したか分からない。
- 参照先が正しい情報を記載していない。
- 参照先を理解できず、誤った利用をしている。
インターネットや文献を検索し、他者の知見を活用することは良いことです。 ライブラリ化されたもの、ブログに記載のあるものなど、どんどん活用するべきだと考えています。
ただし、そういったものを使うときはどこを見て作成したか必ず記載してほしいです。
私のお願いしている方法では、他者のブログや記事を参照した箇所には
// see also: {URL}
という参照先を追記してもらっています。
レビュー時にこちらを見て、参照先は問題ないか、正しく使えているかを判断しています。
昨今の傾向として Qiita などの技術ブログだけをみて、公式リファレンスを読まない人が多いです。
Qiita などの情報は全てがすべて正しい訳ではなく、筆者が誤ったままの認識で記載するものもあります。
1次情報、例えば公式のリファレンスなど、全編英語などの場合もありますが必ず参照し、リファレンスを付けられるようにしてほしいです。
また、参照先も記載されており、正しい情報を参照しているのに使い方を間違えている場合もあります。
使用方法に注意のあるライブラリなどは、その旨がドキュメント内に記載がありますので必ず全文読む癖を付けていただきたいです。
また、以下の処理は特別な処理が必要な場合が多いので、注意してください。
-
大容量データを扱うもの
- メモリを枯渇させないため
-
上限のない繰り返しデータを扱うもの
- 無限にデータが増えることを抑止するため
-
処理に時間がかかるもの
- 無限に待機することを抑止するため
コーディング規約に従っている
コーディング規約を知らない人がいます。
動作的にはなんの問題も無いのですが、レビューが出来ず、レビュー時の認識間違いが発生します。
言語や会社によってコーディング規約は変わります。 ただ大きく変わるものは無いため以下のところは覚えて頂きたいところです。
-
変数
- 変数名は先頭を小文字で書いてください。
- const 宣言していても、変数扱いであればこのルールです。
- e.g.
num
-
クラス名
- クラス名は、先頭を大文字で書いてください
- e.g.
Pet
-
インスタンス名
- クラスを実体化したインスタンスは変数と同様に先頭を小文字で書いてください。
- e.g.
pet
-
定数
- 一度初期化後、絶対に変更しないものは全て大文字で書いてください。
- e.g.
PI
その他にも細かいルールがありますので、各言語ごとの コーディング規約や会社の規約を確認するのが良いです。
なんでこんな誰もが知っていることを書くか。 わたしは、変数を全て大文字で書かれたコードを渡されて読めなかったからです。 その人は、今までコーディング規約というものを知らなかったとのこと。
プログラム内のロジック粒度が揃っている
こちらもあまり指摘されることは少ないかな思うのですが、レビュー時にロジックの粒度が揃っていないプログラムは認識が難しいです。
- 入力チェック、出力の整形はロジック部から分離する
- 関数内の処理を確認し、処理の長いものがあったら分離する。
たとえば、以下の要件のプログラムを作ります。
- 別途作成される関数
getNumber()
から整数を取得する。 - 値が 正の整数であれば FizzBuzz を処理し、
答えは: {値}
の形式で出力し、最初から繰り返す。 - 値が 0であれば、
終了します
と出力し、処理を終わる。 - 値が 負の整数であれば、
値が異常です
と出力し、最初から繰り返す。
/**
* 要件
* - 別途作成される関数 `getNumber()` から整数を取得する。
* - 値が 正の整数であれば FizzBuzz を処理し、 `答えは: {値}` の形式で出力し、最初から繰り返す。
* - 値が 0であれば、 `終了します` と出力し、処理を終わる。
* - 値が 負の整数であれば、 `値が異常です` と出力し、最初から繰り返す。
*
* FizzBuzz の定義 see also: https://ja.wikipedia.org/wiki/Fizz_Buzz
*
* @param num {number} 計算する値
* @returns {number|string} Fizz、Buzz、FizzBuzz または数値
*/
const logic = () => {
while(true) {
// 別途作成される関数 `getNumber()` から整数を取得する。
const num = getNumber()
// 値が 正の整数であれば FizzBuzz を処理し、 `答えは: {値}` の形式で出力し、最初から繰り返す。
if ( num > 0) {
// num が 3 と 5 で割り切れる場合
// 先に計算が必要
if ( num % 3 == 0 && num % 5 == 0 ) {
return "答えは: FizzBuzz"
}
// num が 3 で割り切れる場合
else if ( num % 3 == 0 ) {
return "答えは: Fizz"
}
// num が 5 で割り切れる場合
else if ( num % 5 == 0 ) {
return "答えは: Buzz"
}
// その他
else {
return `答えは: ${num}`
}
}
// 値が 0であれば、 `終了します` と出力し、処理を終わる。
else if ( num === 0 ) {
console.log("終了します")
return
}
// 値が 負の整数であれば、 `値が異常です` と出力し、最初から繰り返す。
else if ( num < 0) {
console.log("値が異常です")
}
}
}
間違えていないですが、読みにくいです。 これを、入力、出力、処理に分けるだけで格段に読みやすくなり、再利用も可能となります。
プログラムの中で、特定の処理だけ記述が長くなるようであれば、分離を検討してください。
/**
* 要件
* - 別途作成される関数 `getNumber()` から整数を取得する。
* - 値が 正の整数であれば FizzBuzz を処理し、 `答えは: {値}` の形式で出力し、最初から繰り返す。
* - 値が 0であれば、 `終了します` と出力し、処理を終わる。
* - 値が 負の整数であれば、 `値が異常です` と出力し、最初から繰り返す。
*
* FizzBuzz の定義 see also: https://ja.wikipedia.org/wiki/Fizz_Buzz
*
* @param num {number} 計算する値
* @returns {number|string} Fizz、Buzz、FizzBuzz または数値
*/
const logic = () => {
while(true) {
// 別途作成される関数 `getNumber()` から整数を取得する。
const num = getNumber()
// 値が 正の整数であれば FizzBuzz を処理し、 `答えは: {値}` の形式で出力し、最初から繰り返す。
if ( num > 0) {
const result = fizzbuzz(num)
console.log(`答えは: ${result}`)
}
// 値が 0であれば、 `終了します` と出力し、処理を終わる。
else if ( num === 0 ) {
console.log("終了します")
return
}
// 値が 負の整数であれば、 `値が異常です` と出力し、最初から繰り返す。
else if ( num < 0) {
console.log("値が異常です")
}
}
}
/**
* FizzBuzz を計算します。
* see also: https://ja.wikipedia.org/wiki/Fizz_Buzz
*
* @param num {number} 計算する値
* @returns {number|string} Fizz、Buzz、FizzBuzz または数値
*/
const fizzbuzz = (num) => {
// num が 3 と 5 で割り切れる場合
// 先に計算が必要
if ( num % 3 == 0 && num % 5 == 0 ) {
return "FizzBuzz"
}
// num が 3 で割り切れる場合
else if ( num % 3 == 0 ) {
return "Fizz"
}
// num が 5 で割り切れる場合
else if ( num % 5 == 0 ) {
return "Buzz"
}
// その他
else {
return num
}
}
分離して書いた場合、 fizzbuzz として機能が独立したため管理がしやすくなります。
例えば、表示方法が変更になったりする際も、表示箇所のみの修正となり、 fizzbuzz の修正がなくなります。
可能な限り const
宣言がされている
こちらも可読性を上げるためのノウハウです。
変数には書き換え可能な変数と、初期化後の書き換えが不可能な変数があります。
特に理由がない場合、書き換え不能な変数宣言を用いて宣言をしてください。
実際のプログラムにおいて、書き換え可能な変数が必要になることは少ないと認識しています。 個人的には昨今では for や while の繰り返し条件に若干利用する程度に収まっています。
const
宣言がされている場合、レビュー時にはそれ以降では書き換えがないことが保証されているため、追加の確認が少なくなります。
また、書き換え可能な変数を利用する場合でも、変数を全く異なる用途に使い回すことはしてはいけません。
冗談みたいな話ですが、私がレビューしたもので 1つの変数を最初から最後まで使いまわしして利用したプログラムをレビューしたことがあります。
もちろん、どの箇所でも全く異なる用途で使われていたため、どこで何が記録されたのか全てメモに取りながら見る必要がありました。
このような状態にしないためにも、変数は特に必要が無い限り、書き換え不可能な状態で宣言をお願いいたします。
目的にあった機能を選択する事ができる
こちらは、テクニックの側面が強いです。
冗長で長い表現は、確認するのが大変なこともあり、短くて完結でより定義に近い表現がある場合、そちらを利用して頂きたいと考えています。
昨今のプログラムでは、よく利用する目的に沿って機能が拡張してあります。 こちらの機能を利用し、簡潔に記載が出来ると可読性が向上します。
-
配列操作関数
-
map
- 配列から新しい配列を作成する
-
reduce
- 配列から値を作成する
-
filter
- 配列からテストに合致する値のみ取り出す
-
foreach
- すべての配列に対して操作する
- e.g.
for (const item of items)
-
slice
- 配列の一部要素を Shallow copy (浅いコピー) する
-
flat
- ネストした配列を単一配列に変換する
-
-
Null 関係の関数
-
Optional Chaining
- Null である可能性のある値を参照する
- e.g.
a?.b?.c
-
Nullish Coalescing
- Null であった場合に、別の値を返す
- e.g.
a ?? default
-
このあたりのロジックはまだ認知が低く利用されていない実体があります。
古い記法でも問題はないのですが、目的にあった機能を利用いただくことで何が目的なのかレビュー者に伝えることが出来るため、積極的に利用いただきたいです。
まとめ
プログラムは作成して完了では有りません。
記録が残っており、後任や同僚のかたが参照でき、理解し、改変が可能でなければなりません。
作って完了から、活用できるプログラムを作成出来るよう、心がけていただけると幸いです。
以上
本サイトの記事について
本サイトの記事は、プログラミングだけでなく、雑多な雑学を多く配信しております。
もしよろしければ、ほか記事も閲覧いただけますと幸いです。