僕のメモ帳

僕のメモ帳です。

Goの勉強 肥満度判定BMI計算をしてみよう 改造編 ~関数化してみよう~

こんばんわ。たかしです。

今日は、ここまで作ってきたBMI計算のコードで、関数化できるところは関数化してしまおうという記事です。

前回の記事はこちらです。
bokunomemocho.hatenablog.com

では、行ってみましょう。

そもそもなんで関数化するの?

僕自身、C言語を使っているエンジニアなもので、関数という言い方をしています。
Goだと別の言い方なんですかね?調べたけど関数で普通に引っかかったのでこのまま関数で書き進めていきます。

まずは、そもそもなんで関数化する必要があるのかについて考えてみましょう。

重複している処理を1つの関数としてまとめてしまうことで、ソースコードを見やすくする(解析が容易になる)ことや、処理の複雑化を防ぐことができる。というメリットがあげられます。

実際に、前回までに作成したBMI計算のコードを見てみると、処理が重複しているところがありますね。
そうです。身長を入力するところと、体重を入力するところです。
この重複しているところを一つの関数にまとめてしまうことで、やりたいことが明確になってわかりやすいソースコードになるわけですね。

では、実際に関数化していきましょう。

処理を関数化するよ!

さあ、先ほども書きましたが、BMI計算のコードには重複処理が存在しているので関数化してしまいましょう!

まずは重複しているfor文の中で、身長と体重で違うところは。

_, err1 := fmt.Scan(&height)
_, err2 := fmt.Scan(&weight)

ここと

fmt.Printf("身長を入力してください:")
fmt.Printf("体重を入力してください:")

この部分ですね。

これらの部分は、パラメータでもらうようにしてしまいましょうか。

そして

if count == 5

この部分も、パラメータで何回まで許容するのか受け取れるようにしてしまいましょう。

また、正常に処理を行えたかをmainに返したいので、正常に終了した場合は0。指定回数ちゃんと入力されなかったら-1を返すようにしましょうか。

これらを踏まえると、関数の宣言はこのようになります。

func GetNum(num *float64, msg string, max_count int) int {
}

一つずつ説明していきましょう。

「GetNum」というダサい名前はおいておいて…
まず、関数はこのように宣言します。

func 関数名(引数があれば指定。複数指定可) 戻り値の型 {
}

例を示すと

// 引数なし、戻り値なし
func hoge1() {
}

// 引数あり、戻り値int
func hoge2(hoge int) int {
    return 0
}

こうなります。

つまり

func GetNum(num *float64, msg string, max_count int) int {
}

こいつは「GetNumという名前の関数です。引数は3つあって、順番に*float64, string, intですよ。戻り値はintですよ。」という意味になります。
ここで、*float64というのが出てきましたが、これは「ポインタ」というものです。今回は御まじないみたいなものだと思ってください。

では、GetNum関数の中に、身長のfor文を移動してきましょう。

func GetNum(num *float64, msg string, max_count int) int {
    for {
            fmt.Printf("身長を入力してください:")
            _, err1 := fmt.Scan(&height)

            // エラー判定
            if err1 != nil {
                fmt.Print("数字で入力してください!\n")
                count++
                if count == 5 {
                    fmt.Print("ちゃんと入力してくれないので処理終了しますね>。\n")
                    os.Exit(1)
                }
            }else{
                // 正しく入力されたらループを抜ける
                break
        }
    }
}

まずは、こいつらのうち、身長と体重で異なる部分を引数で埋めちゃいましょう。
あと、err1って言うのもダサいので、errに直しちゃいましょう。

func GetNum(num *float64, msg string, max_count int) int {
    for {
            fmt.Printf("%sを入力してください:", msg)
            _, err := fmt.Scan(num)

            // エラー判定
            if err != nil {
                fmt.Print("数字で入力してください!\n")
                count++
                if count == 5 {
                    fmt.Print("ちゃんと入力してくれないので処理終了しますね>。\n")
                    os.Exit(1)
                }
            }else{
                // 正しく入力されたらループを抜ける
                break
        }
    }
}

さて、次は入力上限を指定しましょうか。
そういえば、for文って「初期化、終了条件、計算式の構造」の形でも書けましたよね。
そうです。max_countまで処理してしまったら異常終了。それまでに正しく入力されたら正常終了。
このように書いてしまえば、わかりやすいのではないでしょうか。

あと、max_countまで処理してしまった場合に出している文言は、呼び出し元で表示するようにしてもらって消してしまいましょう。

func GetNum(num *float64, msg string, max_count int) int {
    for count := 0 ; count < max_count ; count++ {
        fmt.Printf("%sを入力してください:", msg)
        _, err := fmt.Scan(num)

        // エラー判定
        if err != nil {
            fmt.Print("数字で入力してください!\n")
        }else{
            // 正しく入力されたら正常終了をする
            return 0
        }
    }

    // 最後まで正しく入力されなかったら異常終了
    return -1
}

うん。とってもシンプルになりましたね。

次は、呼び出し元の修正です。
身長と体重、それぞれでGetNumを呼び出してみましょうか。

err := GetNum(&height, "身長", 5)
err = GetNum(&weight, "体重", 5)

これで呼び出すことができます。

あとは、errで受け取った戻り値を判定して、異常終了ならメッセージ出力して処理を終了しましょうか。
これらを踏まえると、全体の処理はこうなりました。

// パッケージ名
package main

// パッケージ取り込み
import (
        "fmt"
        "math"
        "os"
)

// 体重と身長を指定
var weight float64
var height float64

// 入力受付
func GetNum(num *float64, msg string, max_count int) int {
        // 入力処理
        for count := 0 ; count < max_count ; count++ {
                fmt.Printf("%sを入力してください:", msg)
                _, err := fmt.Scan(num)

                // エラー判定
                if err != nil {
                    fmt.Print("数字で入力してください!\n")
                }else{
                    // 正しく入力されたら正常終了をする
                    return 0
            }
        }

        // 最後まで正しく入力されなかったら異常終了
        return -1
}

// mainの定義
func main() {
        // 身長を入力
        err := GetNum(&height, "身長", 5)

        // エラー判定
        if err != 0 {
                fmt.Print("ちゃんと入力してくれないので処理終了しますね。\n")
                os.Exit(1)
        }

        // 体重を入力
        err = GetNum(&weight, "体重", 5)

        // エラー判定
        if err != 0 {
                fmt.Print("ちゃんと入力してくれないので処理終了しますね。\n")
                os.Exit(1)
        }

        var hm = height / 100.0 // 身長をmにする
        var bmi = weight / math.Pow(hm, 2) // BMIの計算
        var bestW = math.Pow(hm, 2) * 22.0 // 適正体重の計算
        var per = weight / bestW * 100 // 肥満度の計算

        // 結果表示
        fmt.Printf("BMI = %f, 適正体重 = %f, 肥満度 = %.0f\n", bmi, bestW, per)
}

やりたいことがわかりやすくて見やすいですねぇ。

では、実際に動かしてみましょう。

$ go build bmi4.go
$ ./bmi4
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
ちゃんと入力してくれないので処理終了しますね。
$
$
$
$ ./bmi4
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:178
体重を入力してください:b
数字で入力してください!
体重を入力してください:b
数字で入力してください!
体重を入力してください:b
数字で入力してください!
体重を入力してください:b
数字で入力してください!
体重を入力してください:b
数字で入力してください!
ちゃんと入力してくれないので処理終了しますね。
$
$
$
$ ./bmi4
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:a
数字で入力してください!
身長を入力してください:178
体重を入力してください:b
数字で入力してください!
体重を入力してください:b
数字で入力してください!
体重を入力してください:b
数字で入力してください!
体重を入力してください:b
数字で入力してください!
体重を入力してください:98
BMI = 30.930438, 適正体重 = 69.704800, 肥満度 = 141

うん!うまく動いた!成功ですね。

今回は、関数化してみようと題して、Goの関数化のやり方について勉強をしました。
次回は、途中でぽろっと話した「ポインタ」について勉強したいと思います。

ではまた次回!

■たかしの懺悔室
僕が適当にGoを勉強しているものだから、前回までエラー判定用の変数をerr1, err2としていましたよね。
これね、色々調べてみると、:=の使い方を間違えていることがわかりました。

// これは変数宣言+値代入
err := 0

// これは値代入
err = 1

ということらしいです。

つまり、わざわざ

err1 := 0
err2 := 1

なんてことをしないで

err := 0
err = 1

こうしていればよかったのです。

とはいえ、今回はチョンプロなので:=でもOKですが、今後のことも考えると

// エラー判定用
var err int

// 関数呼び出し
err = GetNum(&height, "身長", 5)

// エラー判定
if err != 0 {
}

このように書いたほうがわかりやすいですよね。
今後注意しよーっと。