2024-09-27 –, 4F Track4
コンピュータに詳しいメンバーが便利な業務システムを作ってくれたけど、そのメンバーがいなくなった後で困る、というのはよくある話です。とはいえ、書いてくれたコードを理解することができれば、そのシステムを維持していくことはなんとか可能です。
私の部署でも5年前、コンピュータに詳しいメンバーがパソコンサーバーを組み立て、Ubuntuをインストールしてそのマシン上にPythonで便利な業務システムを作ってくれました。そのメンバーが抜けた後も、残されたメンバーでドキュメントを整備したり、テストを書いたりしてなんとか維持運用してきました。でも動かなくなってしまうと業務上困るので、OSやPythonのバージョンはそのままにしていました。いわばツケを5年にわたって溜め続けている状態でした。
そんな中、気がつけばUbuntuもPythonもサポート期間が終了してしまい、パソコン本体の保守期間も切れたため、システムを別マシンに移行することが決まりました。
このトークでは5年分の環境変化に一気に対応するために生じた様々な問題(詳細欄参照)を解決するための苦労話を通じて、みなさんが同じ轍をふまないための教訓をお伝えしたいです。
トークを通じて伝えたいこと
問題なく動いているシステムを修正するリスクをとるのは怖い。特にドキュメントもテストもなく、書いた本人もいなくなったコードが業務で使われている場合はリスクしかない、と言っても良い。そういう状況下ではpandasやnumpyといったパッケージがバージョンアップしたというだけの理由で新しいバージョンに対応するためだけの修正は先送りされがちである。
私は、「ドキュメントなし、テストなし、書いた本人もいない」という状態で引き継いだコードにドキュメントをつけ、テストを書き、自分以外のメンバーもそのコードをメンテナンスできるようにレクチャーをしてきたが、パッケージのバージョンアップ対応は避けてきていた。言い訳になるが工数的な余裕がなかったこともあるし、非エンジニア組織なので周囲の理解が得にくいという状況もあってリスクをとる勇気がなかった。
今回、諸事情から別マシンへの移行を余儀なくされるにあたり、移行のための工数確保が認められたので、これ以上の先送りはやめよう、とPythonのバージョンを3.6から3.11に上げる決断をした。
このトークでは移行にあたって直面した、さまざまな問題の中から、サードパーティパッケージのバージョンアップに伴って発生した問題点5つに絞ってご紹介する(OSやネットワーク環境の変更にともなう問題もいろいろあったが割愛)。
問題点配下の通り(トークでは原因をどう見つけたかも含めて説明予定)
問題1:pandasのエラーチェックが厳しくなった
pandas.DataFrame.to_dict()関数でエラー発生。原因はorientパラメタのtypo。以前のpandasでは1文字目だけ見て判断していたので"records"とすべきところが"record"でも"recrods"でも動作していたが、pandas2.0以降は”records”と正しく書かないとエラーになる。typoを修正することで解決
問題2: scikit-learnのモジュールのパラメータ仕様が変わった(3分)
sklearn.linear_model.LogisticRegressionクラスのsolverパラメータのデフォルト値が以前はliblinearだったのが0.22以降lbfgsに変わったため、penalty="l1"を指定するとエラーになる。solver="liblinear"と明示的に指定することで解決
他にもimblearn.under_sampling.RandomUnderSamplerクラスのratioパラメータ廃止など、複数のモジュールで問題発生。個別に解決した
問題3: mongodbのAPI変更に伴いpymongoの関数も変わった(3分)
以前のpymongoではcollection.save()関数で、レコード追加なのかレコード更新なのかをいい感じに判断してくれていたがMongoDB4.2以降でdb.collection.save()というAPIが廃止されたためpymongoでも使えなくなった。insert_one()関数かreplace_one()関数かを明示的に指定することで解決
問題4: PyPIのborutaパッケージが2019年以降更新されておらず最新のnumpyに対応していない(3分)
PyPIのborutaパッケージを呼び出して使っているが、このパッケージが2019年以降更新されておらず、内部でnp.intやnp.floatを使っている。np.intやnp.floatはnumpy1.20で廃止されておりこのままではエラーになる。
パッケージのソースコードにパッチを当て、np.intをnp.int32、np.floatをnp.float64などに修正することで解決
問題5: 保存したpickleを読み込むとエラー(5分)
機械学習で作成したモデルをpickle形式で保存し、後日呼び出せる仕組みになっていたが、scikit-learn0.23のときにあったsklearn.preprocessing.dataモジュールがscikit-learn1.3では名前が変わっており過去に作ったpickleを呼び出すとエラーになる(他にもいくつか同様のモジュールあり)
これはデータの問題でありコード修正では解決できない。部署内で相談のうえ、過去の分析結果は新環境に引き継がないという判断をして解決(過去データを呼び出して利用する仕組みは別途構築)
得られた教訓と今後に向けて
ドキュメントもテストもない、という状態で4年前に引き継いだコードにドキュメントをつけ、テストを書き、複数メンバーでコード修正ができるようにPython講習も重ねてきたが、リファクタリングは先送りしていた。
常に、とまでは言わないが定期的に最新の環境下でリファクタリングが実施できていれば、今回紹介した5つの問題に同時に直面する、ということはなかったはず。
安心してリファクタリングができるようになるためには、ドキュメントやテストがちゃんとしているということは前提。新規に作るものはドキュメントとテストを最初からきちんと用意し、その上で定期的なリファクタリングをおこなうための工数を確保していきたい。
目次と30分の時間配分案
- 自己紹介(2分)
- 概要説明(5分)
- 発生した問題とその解決方法(解決難度の低い順。5つの問題計16分)
- 問題1: pandasのエラーチェックが厳しくなった(2分)
- 問題2: scikit-learnのモジュールのデフォルトパラメータが変わった(3分)
- 問題3: mongodbのAPI変更に伴いpymongoの関数も変わった(3分)
- 問題4: PyPIのborutaパッケージが2019年以降更新されておらず最新のnumpyに対応していない(3分)
- 問題5: 保存したpickleを読み込むとエラー(5分)
- 得られた教訓と今後に向けて(5分)
(バッファ2分)
誰かが書いてくれたコードを、その人がいなくなった後も維持運用し続ける必要があった場合、そのコードを大きく修正するのはとても怖いことです。そのコードが現状問題なく動いており、業務上必須であればあるほど修正のリスクを取りづらくなります。
Pythonや各種パッケージのバージョンはどんどん上がっていきますが、問題なく動いているシステムをわざわざいじって新しいバージョンに対応させるための修正も同じ理由で先送りされがちです。この傾向は非エンジニア組織ではさらに顕著になります。
私の部署でも5年前、コンピュータに詳しいメンバーがパソコンサーバーを組み立て、Ubuntuをインストールしてそのマシン上にPythonで便利な業務システムを作ってくれました。そのメンバーが抜けた後も、残されたメンバーでドキュメントを整備したり、テストを書いたりしてなんとか維持運用してきました。でも動かなくなってしまうと業務上困るので、OSやPythonのバージョンはそのままにしていました。
そんな中、気がつけばUbuntuもPythonもサポート期間が終了してしまい、パソコン本体の保守期間も切れたため、別マシンに移行することが決まりました。当然OSやPythonのバージョンも大きく変わります。
このトークでは5年分の環境変化に一気に対応するために生じた様々な問題を解決するための苦労話を通じて、みなさんが同じ轍をふまないための教訓をお伝えしたいです。
-
パッケージのバージョンアップ等でコードがうまく動かなくなったときの対応事例
-
今まで問題なく動いてきたシステムを、将来も動かし続けるために、今やっておくべきこと
特にありませんが、他人が書いたコードの保守メンテナンスを経験された方は、ぶつかった問題に深くうなづいていただけると思います。
Audience experiment:Beginner
Language of presentation:日本語
Language of presentation material:日本語