Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
6
Help us understand the problem. What is going on with this article?
@44x1carbon

日本語プログラミングのすゝめ

More than 1 year has passed since last update.

日本語プログラミングのすゝめ

この記事は 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で表されます。
    • スペアは/で表されます。
    • ガターは-で表されます。
    • ボーナスボールは||以降の文字列がボーナスボールです。
      • ボーナスボールが投球数分の投球結果が追加されます。
  • 最終的には、ゲームのトータルスコアを数値として出力します。

コード

仕様と突き合わせながら読んでみてください。

スコアシート.kt
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)
}
フレーム.kt
package ボウリング

sealed class フレーム {}

object ストライク : フレーム() {
    val 一投目: Int = 10
}
data class スペア(val 一投目: Int): フレ() {
    val 二投目: Int = 10 - 一投目
}
data class 通常(val 一投目: Int, val 二投目: Int): フレ()
ボーナスボール.kt
package ボウリング

sealed class ボーナスボール

object ボーナスなし: ボーナスボール()
data class 一回ボーナス(val 一投目: Int): ナスボ()
data class 二回ボーナス(val 一投目: Int, val 二投目: Int): ナスボ()

メリット、デメリット

日本語で書かれたコードはどうでしたか? 人によっては読み易かったり、読みに難かったりしたかもしれません。
日本語で書くメリットとデメリットについて僕なりにまとめてみました。

メリット

  • 名前を考える時間が短く済む。
  • 名前の違和感に気付きやすい。
  • 名前のつけ忘れに気付きやすい。
  • 仕様との表記揺れに気付きやすい。

デメリット

  • 複数形の表現が難しい
  • クラス、メソッド、変数、固定値の見分けが付きづらい
  • 英数、日本語切り替えがめんどう
  • 補完が効かない

まとめ

日本語プログラミングにより、仕様と突き合わせて理解や確認はしやすくなったかと思います、なにより仕様とのズレを認識しやすくなると言うのが最大のメリットかと考えています。
ただその反面、プログラムとして書きづらかったり読みづらくなっていると思います。

日本語プログラミングまで行かなくても、「仕様書と突き合わせてコードを理解しやすくする」為にクラス名やメソッド名はしっかりと考えていく必要があると思いました。

6
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
44x1carbon
KotlinやDDDを勉強中です

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
6
Help us understand the problem. What is going on with this article?