タプル生成

やっと、いくらかエラーの根本原因がいくつか見えるようになって来たのが、tupleが作られちゃうバグ。この話題は、少し前に書いた。

記述を書き直す際に、つい、リストから持って来て代入文を書いたりする。(Typoがあるから、この行為自体は別にそんなに悪いことじゃないと思う。)リストの区切りにカンマがなければ、その場所がエラーだと、コンパイラが騒いでくれるし、Pycharmでもsyntax errorを出してくれる。それはいいんだが、その逆が問題。

代入分の行末のカンマ、エラーにならないで、タプルが生成される。言語仕様なんだ。ようやっと、いくらか慣れて来たけれども、見落としたままわからずに、これも数時間悩んだのは、クラスの初期化で部分的にタプルが渡ってしまった時。あろうことか、そのクラスインスタンスが stringに解釈されちゃう、という現象が起きた。

3段階くらい別の場所で、stringには、そんなメソッドはないぞ、というエラーが出て来て、開き直って、30箇所くらいにprint文を入れて辿っていったら、インスタンスの初期化変数の一つが上述の原因でタプルになっていた。こんなところに虫がいたかぁ・・・しかも、悪いことにそのインスタンスの別のメソッドは生きていたりしたから、全くわからなかった。

こうやって、つまらない(全然、つまらなくないけど)バグを出すから、もう、想定外に時間が無駄に浪費されてしまう。完全に「慣れる」まで、あと、どれくらい時間がかかるんだろうか。pythonそのものは、十分に理解しているつもりなんだが、こういう「バグの出方/現象の出方」それ自体のデータベースが、頭の中にまだない、です。

ジジイです。悔しい。何よりも悔しいのが、つい、Ruby on Railsのつもりで「納期予測」を出してしまった自分の甘さ。寝てる間ないなぁ。甘い。砂糖のシロップ漬け。絶叫!

医療用アプリを書いていた頃、再三、販社の人に「読みの甘さ」を指摘されていたのに・・・学習してない。今はもう、サラリーマンじゃないですからね。ゴメンじゃ済まない。

初パートナー

記憶の泥沼。

僕が初めて、自分の書いたプログラムを「商品」として売って頂いたのは、大学4年の時だったと思う。CANON販売の代理店をしている方だった。細かい話を飛ばせば、今と全く同じ、バグは出る。営業さんは振り回されて、ご迷惑をおかけする、けれども、最後はきちんと動作させた。ある時、その方が、「漢字が使えるプリンターがあるから、それに対応して欲しい。」とおっしゃった。「買い取ってもらえたら、自分のものとして使える」という一言に乗って、2回分くらいの売上(当時の50万円)と相殺する形で、「廉価版」の漢字プリンタ、EPSONのMPシリーズだったか(確か、MP-8000Kだったかなぁ)を買った。

その半月くらい後に、そのEPSONから、上位互換のUPシリーズだと思ったけれども、格安の漢字プリンタが発売された。性能はかなり上で、価格は、僕が買った50万円から30万円にまで安くなっていた。何のことはない、僕のソフトウェアの価格、ほぼ1本分が安くなっていて、何だか、その方の「在庫」を僕が買い取って、お客様に2件売り上げた分は、丸ごと、新製品発売の、半月前の旧タイプのプリンタに化けて、相殺になった。

すぐには気付かなかったけれど(鈍いです、僕は。馬鹿です。)その方は、ソフトウェアの販売と抱き合わせで、EPSONのパソコン(QC-10)も、プリンタも、お客様に売って稼いでいたはずなのに、そのパソコンを売るために搭載した僕のソフトウェアの売上を僕に支払うことなく、そっくり「相殺」で、旧型プリンタの在庫処分に僕を使ったんだろうなぁ。僕の、営業職嫌いは、あの時から始まった。いくら仕事をしても、全然金にならない。確かに、仕事の道具は自分のものになったけれども、なんだか、モヤモヤした感じだけが残った。

その後知り合った営業職の方々には、とても誠実なお付き合いをさせて頂いたけれども、あの、「最初」のインパクトは、今でも忘れられない。「いい経験をさせていただきました。ありがとうございます。」そうなんですけどね、僕の「営業職嫌い」は、たぶん、その時に始まった。若いSEとかプログラマの方々、こういうこともあるんだよ、というお話として、知っておいて欲しいなと思った。

経験することはとても大切だけれど、学習しなかったら意味ないなぁ、なんてことも思った。2年前の場合には、その方の営業力がなかっただけで、悪意はなかったんだろうなぁ。作り続けたけれども、一円にもならなかった5本のアプリ。

世の中、こういうこともあります。ぜひ、若いSEさんや、プログラマさんの参考にしていただければ。

Integer Field の Null値

結論から言えば、Django自身の機能を使う場合には、相当に細かいところを押さえていないといけない、ということなんだと、理解した。

PostgreSQLの DBのIntegerフィールドにNull値を入れる。あまり使いたくなかった。

cut_off = models.IntegerField(verbose_name='締め日', default=25, blank=True, null=True)

defaultを、Noneではなく、25として最初定義した。これだと、blank=True, null=Trueの意味がない、という結論に、試行錯誤の末、辿り着いた。なぜ、31にしなかったか。だって、2月とか、4月とか、31だと問題でしょうが・・・。「月末」を締め日にするには、カレンダー機能の翌月1日を求めてから1日戻す。そうすれば、閏年計算なんかしなくても、末日は求められるけれど、ここでの「締め日」は、「参考データ」なので、「あらゆるフィールドが省略可能」という前提だと、31はやめて25を入れておけば良いか、という安易な判断が墓穴を掘った。(参考データだとしても、結構パターン化した処理手法は、あるんだろうなぁ。)

まだ、Djangoを使いこなしていなかった頃(今でもそうかなぁ・・・) get methodとpost methodで、処理の大半を書いていた。(PHPのZendの流れ、Ruby on Railsでも、そんな感じだった。)その時点では、正常動作していた。

ある程度慣れてきて、どこかの段階で、これを form_valid 内部での処理に置き換えた。それでバグが出た。再現しなかったと思ったけれども、情報はきちんと提供されていました。不注意だ。これに悩まされた。

invalid literal for int() with base 10: ''

このエラーは、空白文字列を整数に変換しようとして起きる。ところが、getに失敗した際にはわざわざNoneを与えるように(postで処理していた時の流れで)書いているし、Noneを受け付けるはずなのに、これが出てしまう。ダミーの画面フィールドを設定して、処理して置き換えるようにすると、「そんなフィールドは定義されていない」のエラーに変わるだけ。細かいなぁ。ググりまくったけど、全然ヒットしない。

開き直って、これに変えた。defaultをNoneにした。

cut_off = models.IntegerField(verbose_name='締め日', default=None, blank=True, null=True)

これで解決した。データベースのIntegerフィールドにNoneを入れる、っていうのに、なんとなく抵抗があった。というか、昔だったらそんなことをしたらエラーにされた気がした。だから、0を入れるのが常套手段だった気がする。ところが、0を入れると、画面に「締め日: 0」と出てしまう。空欄にならない。やめときゃいいのに、こっちを修正しようかとも思ったけれども・・・綺麗な逃げ方が見つからない。

しこたま迷走した挙句、出した答え。Null値を受け入れるならば、defaultをNoneにする。当たり前って言ったら、確かに当たり前の答えだった。「締め日」を空白にする、なんていう発想が最初になかったから、25を入れた、それがそもそもの誤り。最初は無難に動いていたから、疑いすらしなかった。それが、form_validでの処理に切り替えた途端に、Noneの値を強引に(たぶん、default=25のおかげで)整数変換するように動作させてしまった。これがdjangoの動作なんだと、一つ学びました。

わかってしまえば、default=Noneのありがたさ、ということになるけれど、せっかくの「お休み」をたっぷりと潰しました。今回経験したから、次からは同じ失敗はしない。

もう一つの教訓。テストケースを書く時間的余裕が全くないなら、安易にリファクタリングすべきじゃない。「動作しているコード」が全て。サバイバルに近い。美しくなかろうが、見苦しかろうが、正常動作しているならば、触らない方がいい。そんなことはわかっていたけれども、まさか、こんな結果になるとは思わなかった。

すみません、格安です。短納期で、ギリギリまで請求金額を下げています。本当なら、「テスト手順書」か、または、テストScriptをきちんと書いて、バグなんか出したくない。だけど、組み合わせを変えてのテストは割愛しているから、後は経験で対応するしかない。昔のKKD(勘、経験、度胸)の世界です。でも、僕は、バグは潰します。安くしている分、何かが起きたらオンサイトで対応します。絶対にバグは潰します。そこにバグがあることがわかるなら、潰せないバグはない。それだけは、声を大に主張したい。

できることなら、テストスクリプトをきちんと書いて、絶対に客先ではバグは出さない、そうしたいけれど、それをやるには、納期と料金を、今の1.5〜2倍くらいにしてもらわないと、ならないと考えています。というのが、弊社の状況。安くて、短納期の要望を出されるのならば、バグ潰しにご協力願いたく存じます。

同業者の方、こういうこともあるのかと、ご参考になさってください。そのうち、会社のサーバを引っ越したら、Qiitaあたりに土俵を移しますが、今はまだ、サイトすら整理している余裕がないので・・・


これ、違ってました。サーバをリブートしたら、また同じエラーが出た。原因はこれでもなかった。ついに、これだけで1日以上潰すか・・・

逃げました。「締め日」を、IntegerFieldから、CharFieldに変えた。データベース上でのフィールドの型も変えて、migrateした。

そもそも、日付として扱わない「日付」で、Nullの可能性があるなら、最初からIntegerではなくCharにすべきだった。これなら、万一「日付として使う」としても、その都度変換したらいい、だけだった。汚ねぇコードだなぁ・・・でも、これなら確実に動く。