バグの見つけ方・潰し方・予防法・よくあるエラーなどを紹介する。
あくまで独学なので注意。
もくじ
バグの原因の見つけ方
- エラーログを見る
- 情報エディターで見れるログより、コンソールで見る方が多い
処理途中の動作を見る
print() やself.report({'INFO'}, "hoge")を活用する。
- if 文などの分岐がどこをたどっているのかを調べる
- 変数が意図した通りの内容になっているかを確認する
- 完成版公開時には不要なので、コメントアウトか削除しておくこと
確認方法の違い
- self.report()
- Blenderの画面上に出力されるので、確認しやすい
- str型にする必要があるので注意
- print()
- コンソールに出力されるので、少し確認しにくいが、selfのない関数内でも利用できる
- print(aaa,bbb,ccc)のように、複数の引数を指定できる
- logging
- 動作途中の処理結果をファイル出力する
- クラッシュした際の挙動のログを確認しやすい
- まだ自分は利用したことがないので詳細は不明
コメントアウトする
原因がありそうな箇所をコメントアウトし、少しずつ特定していく。
中々原因が見つからないようであれば、一旦大幅にコメントアウトした方がよい。
- 方法1:1 / 2、3/4、7/8 のように、エラーが起きなくなるまでコメント範囲を大きくしていき、原因箇所を探す
- 具体的な原因がわからないときに、大雑把に特定していくことができる
- 方法2:一旦全てコメントアウトし、最初から少しずつコメントを外して原因箇所を探していく
ソースコードを整理する
ソースコードを整理したり、ひたすら各行に処理の解説コメントを付けていく。
- ひたすら各行に処理の解説コメントを付けていく
- コメントが書きにくい行が自分の中で処理を理解していない所
- 行の整理もでき、ソースコードがみやすくなる
PCから離れる・散歩する・寝る
プログラミングから一旦離れることで客観的に見ることができ、考えがまとまる。
1時間以上同じバグで詰まるのであれば、今解決する無理だと諦めて、一旦別のことをする。
誰かにバグの状況を説明する
他人に話すために自分の中で内容を整理するため、問題点が見つかる。
人でなくても、人形に話しかけたり、Twitterに文章をまとめて投稿したりすることでもよい。
他に人の意見を得ることもできる。
別の環境で実行してみる
問題が環境依存かを検証する。
コンソールで実行してみる
問題の原因だと思われるソースコードの数行だけをコンソールから実行してみる。
スクリプトファイル内から実行することが原因であったり、要素が複雑に絡み合って問題が起きている可能性もあるため、切り分けて実行してみる。
初期設定のアプリで試してみる
初期設定で起動し直して実行指定みる。
そもそも開発環境はなるべくクリーンな設定でテストすることが望ましい。
アプリを再起動する
最新の情報を再読み込みしたはずができていない・削除したはずのなにかが削除されていないなどがある。
あまりにもバグが解消されない時は、アプリの再起動を試してみると案外これだったりする。
OSを再起動する・デバイスを変える
他のアプリでも同様の動作不良になる場合は、OSやデバイスを再起動する・変える。
バグの潰し方
- なるべくif文で状況を限定する
- try: とexcept: で回避する
- 問題が起こっても回避できるので強力
- 開発中はバグが見つけづらくなる問題があるので注意
よくあるエラー
エラーが起こると、エラー文はどの種類のエラーまで言及してくる。
エラー文を理解すれば問題を把握しやすい。
パッと見てエラー内容がわからなくても、エラー文をネットで検索すれば大体は前例が出てくる。
Pythonの基本的なエラー一覧とその原因の確認方法 | note.nkmk.me
SyntaxError
- 書き方が間違っている
- 例:キーボードの打ち間違いで起こりやすい
- 例:if hoge = "hoge": のように、"==" ではなく"="を使っているなど
IndentationError
- インデントがおかしい
- 例:他のソースコードからコピペしたときに起こりやすい
- 解決法:テキストエディタのインデント一括変換を実行する
ModuleNotFoundError
- モジュールが見つからない
- 例:import の書き方が間違っている
- 例:モジュール名が間違っている
- 例:カレントディレクトリからの相対パスの指定が間違っている
AttributeError
- 属性に要素が存在しない
- 'NoneType' object has no attribute '〇〇○'となっている場合は、元々の変数の中身が存在しないためにこのエラーが起こっている
例 : bpy.context.objectには、hogeいう属性は存在しない
bpy.context.object.hoge > Traceback (most recent call last): > File "<blender_console>", line 1, in <module> > AttributeError: 'Object' object has no attribute 'hoge'
例 : bpy.context.objectが存在しないので、nameいう属性も見つけられない
contextは状況によって変化するため、内容が存在しない時はNoneTypeとなる。存在しないものの属性にはアクセスできないのでエラーが出る。
アクティブオブジェクトないときにアクティブオブジェクトのデータにアクセスしようとしたときなどに起こりやすい。
bpy.context.object.hoge > Traceback (most recent call last): > File "<blender_console>", line 1, in <module> > AttributeError: 'NoneType' object has no attribute 'name'
TypeError
- 型が間違っている
- 例:文字列型(str)を要求しているのに整数(int)を指定してしまっている
- 解決法:型を変換する str(int)
バグの見つけ方(デバッグ)
デバッグは想定しうる操作やすべての機能と機能の組み合わせを試してみるのがよいが、すべてやるのは現実的ではない。
実際に自分自身でアプリを利用してみるのがよい(3Dモデル制作支援プラグインなら、実際にモデルを1体作ってみるなど)。
想定されていないような意地悪な動作をあえて試してみることも大事。
バグを予防する
コピペする・スニペットを使う
コピペできる箇所はコピペすること。
スニペットはIMEでいう辞書のようなものであり、短い文字列から事前に用意した長文を入力することができる。
ヒューマンエラーは必ず起こる。
手動でのタイピングでは、タイプミスが生じる可能性がある。
ソースコードを見やすくする
可読性の高さはバグの起こりにくさ・バグの特定しやすさにつながる。
処理内容ごとに関数を分ける
1まとまりになりそうな部分は、なるべく小分けに関数化する。
長文のソースコードは見にくい。
処理ごとに分類すると、ソースコードの流れがわかりやすくなる。
特にif文で処理が大きく分かれる時に効果が高い。
変数の定義は最初にまとめる
変数の定義と処理を分けることで、ソースコードが見やすくなる。
変数を定義する位置がバラけていると、ソースコードを後で移動したときに変数がまだ作られていない箇所に移動してしまったりする。エラーの原因になりうる。
処理の途中で変数を作る場合は1度だけ利用する時のみにする、などと規則を決める。
命名を気をつける
固有の名前をつける。
多少長くなっても固有の名前をつけた方がよい。
モジュール名(ファイル名)・変数名・関数名・引数など、どの場所に使う名前でも被らないようにするのがよさそう。
名前が短いと意図しないところで重複してしまい、エラーの原因になる。
一番最初に定義した変数を上書きしてしまう例
変数名が被って、ソースコードの途中で上書きしてしまう場合がある。
for 文で利用した変数名はfor文の外でも残るので注意。
item = 'hoge' lists = ["a","b","c","d"] for item in lists: item > 'a' > 'b' > 'c' > 'd' item > 'd' # リストの最後のアイテムになる
変数名と引数名がかぶると失敗する例
引数名がindexであるときに、変数名までindexだと、引数の指定をそのままでは利用できない。
ユーザーは変数を書いたつもりが、Pythonは引数を指定していることになって、「引数を設定していない」というエラーになってしまう。
# マテリアルのインデックス番号2番目を削除する(失敗) index = 2 obj = bpy.context.object obj.data.materials.pop(index) > Traceback (most recent call last): > File "<blender_console>", line 1, in <module> > TypeError: IDMaterials.pop(): required parameter "index" to be a keyword argument!
index=index とすれば解決する。
# マテリアルのインデックス番号2番目を削除する index = 2 obj = bpy.context.object obj.data.materials.pop(index=index)