日本語プログラミングのすゝめ
この記事は Yahoo! JAPAN 2018年度新卒有志でつくるYahoo! JAPAN 18 新卒 Advent Calendar 2018 の15日目の記事です。
自己紹介
18新卒の山崎です、Androidのエンジニアをしています。
新卒随一のKotlin好き
ドメイン駆動設計やテスト駆動開発、モブプログラミングなど、より良い開発を行うための手法が好きで勉強しております。
日本語プログラミングについて考え始めたきっかけ
配属され機能修正や追加を行う際に、仕様書と現状のソースコードを突き合わせて理解するのに時間が掛かったので、これから自分が書いていくコードを出来る限り理解しやすいコードにしたいと思ったのがきっかけです。
日本語プログラミングとは?
今回、書く内容はなでしこやプロデルなどの日本語プログラミング言語のお話ではありません。
ユニコードが使えるプログラミング言語で、変数名やクラス名、メソッド名に日本語を使ってプログラミングをすると言うことです。
ここでいうプログラミングは、サービスやシステムを構築するために行うプログラミングを想定しております。
なぜ、日本語なのか?
仕様書のキーワードを、日本語から英語に変換してコードにしますが、英訳を考えるのに時間が掛かったり、人によって訳し方が異なったりと、時間をかけた割にはチームメンバーにちゃんと伝わらないことがたたあります。
仕様書の日本語をそのままコードにすることで考える時間の削減やチームメンバーに伝わりやすいのではないかと思っています。
実際、日本語でプログラミングしてみた
今回はcyber-dojoのボウリングスコア計算のお題を拝借して日本語プログラミングしてみました。
仕様
簡単にボウリングのスコア計算の使用説明をします。
- 1 ゲーム は10個の フレーム で構成されています。
- 1フレームでは最大2投投げることができます。
- 1投目 で10ピン全て倒した場合は ストライク になり、二投目は投げず次のフレームに移ります。
- 2投目 で合計10ピン全てを倒した場合は スペア となります。
- その投球で1ピンも倒せなかった場合は ガター となります。
- 10フレーム目にはボーナスボールとして、更に投球することができます。
- ストライクの場合は、2球
- スペアの場合は、1球
- それ以外は、ボーナスボールはなしです。
-
X|7/|9-|X|-8|8/|-6|X|X|X||81
のような形式で1ゲーム分の スコア が文字列が入力として与えられます。- フレームの区切りは
|
で表されます。 - ストライクは
X
で表されます。 - スペアは
/
で表されます。 - ガターは
-
で表されます。 - ボーナスボールは
||
以降の文字列がボーナスボールです。- ボーナスボールが投球数分の投球結果が追加されます。
- フレームの区切りは
- 最終的には、ゲームのトータルスコアを数値として出力します。
コード
仕様と突き合わせながら読んでみてください。
package ボウリング
class スコアシート(val スコア文字列: String) {
val フレーム達: List<フレーム> = スコア文字列.フレーム部分の文字列を取り出す().フレーム達に変換する()
val ボーナスボール: ボーナスボール = {
val ボーナスボール文字列 = スコア文字列.ボーナスボール部分の文字列を取り出す()
when(ボーナスボール文字列.length) {
0 -> ボーナスなし
1 -> 一回ボーナス(ボーナスボール文字列[0].点数に変換する())
2 -> 二回ボーナス(ボーナスボール文字列[0].点数に変換する(), ボーナスボール文字列[1].点数に変換する())
else -> ボーナスなし
}
}()
fun トータルスコアを求める() : Int {
val 途中経過 = when(ボーナスボール) {
is ボーナスなし -> 計算結果(0, 0, 0)
is 一回ボーナス -> 計算結果(0, ボーナスボール.一投目, 0)
is 二回ボーナス -> 計算結果(0, ボーナスボール.一投目, ボーナスボール.二投目)
}
val 最終結果 = フレーム達.foldRight(途中経過) { フレーム, 途中経過 ->
when(フレーム) {
is 通常 -> {
val 合計 = 途中経過.合計 + フレーム.一投目 + フレーム.二投目
val 次の投球結果 = フレーム.一投目
val 次の次の投球結果 = フレーム.二投目
計算結果(合計, 次の投球結果, 次の次の投球結果)
}
is スペア -> {
val 合計 = 途中経過.合計 + フレーム.一投目 + フレーム.二投目 + 途中経過.次の投球結果
val 次の投球結果 = フレーム.一投目
val 次の次の投球結果 = フレーム.二投目
計算結果(合計, 次の投球結果, 次の次の投球結果)
}
is ストライク -> {
val 合計 = 途中経過.合計 + フレーム.一投目 + 途中経過.次の投球結果 + 途中経過.次の次の投球結果
val 次の投球結果 = フレーム.一投目
val 次の次の投球結果 = 途中経過.次の投球結果
計算結果(合計, 次の投球結果, 次の次の投球結果)
}
}
}
return 最終結果.合計
}
fun String.ボーナスボール部分の文字列を取り出す(): String = this.split("||").getOrNull(1) ?: ""
fun String.フレーム部分の文字列を取り出す(): フレーム文字列 = this.split("||").getOrNull(0)?.let { フレーム文字列(it) } ?: フレーム文字列("")
data class フレーム文字列(val 文字列: String) {
fun フレーム達に変換する(): List<フレーム> {
return 文字列.フレーム毎に分割する().map {
when {
it == "X" -> ストライク
it.length >= 2 && it[1] == '/' -> スペア(it[0].点数に変換する())
else -> 通常(it[0].点数に変換する(), it[1].点数に変換する())
}
}
}
fun String.フレーム毎に分割する(): List<String> = 文字列.split("|")
}
data class 計算結果(val 合計: Int, val 次の投球結果: Int, val 次の次の投球結果: Int)
}
fun Char.点数に変換する(): Int = when(this) {
'X' -> 10
'-' -> 0
else -> this.toString().toInt(10)
}
package ボウリング
sealed class フレーム {}
object ストライク : フレーム() {
val 一投目: Int = 10
}
data class スペア(val 一投目: Int): フレーム() {
val 二投目: Int = 10 - 一投目
}
data class 通常(val 一投目: Int, val 二投目: Int): フレーム()
package ボウリング
sealed class ボーナスボール
object ボーナスなし: ボーナスボール()
data class 一回ボーナス(val 一投目: Int): ボーナスボール()
data class 二回ボーナス(val 一投目: Int, val 二投目: Int): ボーナスボール()
メリット、デメリット
日本語で書かれたコードはどうでしたか? 人によっては読み易かったり、読みに難かったりしたかもしれません。
日本語で書くメリットとデメリットについて僕なりにまとめてみました。
メリット
- 名前を考える時間が短く済む。
- 名前の違和感に気付きやすい。
- 名前のつけ忘れに気付きやすい。
- 仕様との表記揺れに気付きやすい。
デメリット
- 複数形の表現が難しい
- クラス、メソッド、変数、固定値の見分けが付きづらい
- 英数、日本語切り替えがめんどう
- 補完が効かない
まとめ
日本語プログラミングにより、仕様と突き合わせて理解や確認はしやすくなったかと思います、なにより仕様とのズレを認識しやすくなると言うのが最大のメリットかと考えています。
ただその反面、プログラムとして書きづらかったり読みづらくなっていると思います。
日本語プログラミングまで行かなくても、「仕様書と突き合わせてコードを理解しやすくする」為にクラス名やメソッド名はしっかりと考えていく必要があると思いました。