2017/06/29

Recent entries from same category

  1. Golang の archive/zip でタイムゾーンの問題とファイル名の問題が解決した。
  2. Golang で優先度を変えてプロセスを起動する。
  3. net/http でレスポンスの内容を確認したいなら io.TeeReader を使おう
  4. Golang で物理ファイルの操作に path/filepath でなく path を使うと爆発します。
  5. gRPC のサービスが簡単に作れるライブラリ「lile」

今日とある場所で虫が入り込む瞬間を見た。虫といってもバグの方。それはプログラマ向けの Q&A サイトで始まった。質問の内容はこうだ。

アース製薬 アース渦巻香 蚊取り線香 ジャンボ 50巻缶入 アース製薬 アース渦巻香 蚊取り線香 ジャンボ 50巻缶入

アース製薬 / ¥ 628 (2011-03-22)
 
発送可能時間:在庫あり。

文字列には0または4がだけが含まれる。文字列は 4 から始まり、例えば 440, 44, 40, 4400, 4440 など、これらは正しいとするが 404 は正しくない。今のところ、私は 0 の直後に 4 が現れるかどうかでチェックしている。これは果たして効率的だろうか。

始め僕はこの質問文をちゃんと読んでおらず、正規表現を使ってこれを実装した。

package main

import (
    "regexp"
)

func check(s stringbool {
    return regexp.MustCompile(`^4+0*$`).MatchString(s)
}

func main() {
    for _, tt := range []string{"444""44""40""4400""4440"} {
        if !check(tt) {
            panic("want true: " + tt)
        }
    }
    for _, tt := range []string{"404""040"} {
        if check(tt) {
            panic("want false: " + tt)
        }
    }
}

でも質問をよく見たら彼は効率的かどうかを気にしていた。確かにこのお題で正規表現は無い。僕は慌てて以下のコードを付け添えた。

package main

func check(s stringbool {
    i := 0
    r := []rune(s)
    for i = 0; i < len(r); i++ {
        if r[i] != '4' {
            break
        }
    }
    if i == 0 {
        return false
    }
    for ; i < len(r); i++ {
        if r[i] != '0' {
            return false
        }
    }
    return true
}

func main() {
    for _, tt := range []string{"444""44""40""4400""4440"} {
        if !check(tt) {
            panic("want true: " + tt)
        }
    }
    for _, tt := range []string{"404""040"} {
        if check(tt) {
            panic("want false: " + tt)
        }
    }
}

いずれのパッケージにも依存しておらく、おそらくちゃんと動くコードだろう。

フマキラー 線香 本練りジャンボタイプ函入 50巻 フマキラー 線香 本練りジャンボタイプ函入 50巻

フマキラー / ¥ 667 (2007-03-29)
 
発送可能時間:在庫あり。

その後、周りのオーディエンスが質問した彼に「どんなケースか良く分からないな、コードを見せてくれる?」と言った。そして彼は以下のコードを見せてくれた。

package main

import (
    "fmt"
    "strings"
)

func validate(str stringbool {

    if strings.HasPrefix(str, "4") {
        for i := 0; i < len(str)-1; i++ {
            if (str[i] == '0'&& (str[i+1== '4') {
                return false
            }
        }

    } else {
        return false
    }

    return true
}

func main() {

    data := []string{"4""44""4400""4440""404""004"}
    for _, val := range data {
        fmt.Println(validate(val))
    }
}

なるほど彼が最初に言ってた通り「0 の直後に 4 が現れる事でチェック」している。一見このコードは正しそうに見える。でもこのコードは「406」の様な文字列で正しく機能しない。彼にそれを伝えたところ、彼は「@mattn -1 最初に 0 と 4 しか無いっていったじゃん」と返してきた。

僕はここで「あー、バグが混入するタイミングはここなんだ」と思った。例えばこの関数が「4 から始まり 0 が 0 個以上続く文字列をチェックする」関数だとして他のユーザに配られるとする。それを譲り受けた開発者はそれを使ってテストする。この時点でその開発者は「もちろん 406 みたいな文字列も弾いてくれる」と信じてしまうだろう。こうやってバグってのは混入するんだ、と思った。まぁ、もしかしたら彼のキーボードには 0 と 4 とリターンキーしか付いていなかったのかもしれない。

村田屋 蚊取り線香入れ フィッシングモスキートフロッグ 4767 村田屋 蚊取り線香入れ フィッシングモスキートフロッグ 4767

村田屋産業 / ¥ 1,944 ()
 
発送可能時間:在庫あり。


blog comments powered by Disqus