5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PythonでError Handlingをどのような場合でコード上に書くべきか

Posted at

What is this?

備忘録的なあれです。
Golangを書いている後、Pythonを書き始めるとエラーをどうあつかったらいいかわからなくなりました。

Golangは基本的に、なにかメソッドを書く際には、必ずといっていい程、エラーも同時に返り値として設定します。そのおかげで、「このエラーはraiseさせよう」とか、「ここはpanicでええやろ」とか、「ここは例外的に処理させねばとか」、逐一考える機会が与えらえて良いなあという印象を受けました。

一方で、Pythonはコード上にて明示的吐き出されません。プログラムを実行した際に、ようやく「ああ、ここはこういうエラーが吐き出されのね、ふーん」みたいなことが多々ありました。

Golangから改めてPythonを触った自分は、エラー出た場合に、try except で例外処理として、エラーを吐き出すべきなのか、そのままエラーをシステムに吐かせればいいのか、わからなくなりました。

そこで、今回では、どのような場合にエラーをtry exceptでログを吐き出すべきなのか、備忘録的に記載していきます。

エラーを例外処理する場合

結論からいうと、
「エラーログに付加要素を付け加えたい場合に例外処理をするべき」
です。

どういうこっちゃかというと、例えばデータベースに接続して検索するケースを挙げます。
下記のような、Mysqlに接続するとします。

import mysql.connector

class DataBaseRepository:
	def  __init__(self):
		self.conn = mysql.connector.connect(
            database=MYSQL_DATABASE,
            host=MYSQL_HOST,
            user=MYSQL_USER,
            password=MYSQL_PASS,
            charset='utf8mb4',
            autocommit=True,
            connection_timeout=60,
        )

    def get_users(self, user_id: int, group_id: int) -> [str]:
    	"""
    	get username
    	:return list of username
    	"""
    	cursor = self.conn.cursor
    	sql = 
    	f"""
    	SELECT name
    	FROM users
    	WHERE user_id = {user_id}
    	  AND group_id = {group_id}
    	"""
    	cursor.execute(sql)
    	return cursor.fetchall()

    def close(self):
        self.conn.close()

    def __del__(self):
        self.close()

DataBaseに接続して、get_users で user_idgroup_id が一致するuserの name を取得します。(そのほかのメソッドは、説明は省きます)

このコードを用いた下記のような2種類実行のコードがあるとします。

  • 例外処理を書く場合
from logging import getLogger

if __name__ == '__main__':
	logger = getLogger(__name__)

	repo = DataBaseRepository()
	user_id = 100
	group_id = 1
	try:
		user_names = repo.get_users(group_id, gender_id)
	except:
		"""
		ここにどうような処理を書くべき?
		"""

	if len(user_names) == 0 {
		logger.warn('user_name size is zero!')
	}
  • 例外処理を書かない場合
from logging import getLogger

if __name__ == '__main__':
	repo = DataBaseRepository()
	user_id = 100
	group_id = 1
	user_names = repo.get_users(group_id, gender_id)
	if len(user_names) == 0 {
		logger.warn('user_name size is zero!')
	}

どっちがいいと思いますか?
どっちでも、エラーがでるときは十分ありうるコードなのです。DataBaseのカラムが存在しないかもしれないし、Connectionが確立されてないかもしれないし、そもそもレコードがないこともありえますね。

その場合、より多くの情報があった方が不足の事態に対処しやすいです。
この情報が 付加要素 となるわけです。

例えば、今回の場合だと、group_id と gender_idがあった方が、断然エラー対応しやすいです。
なので、エラーがでる部分を例外処理でくくって、付加要素を加えます。

  • 例外処理を書く場合
from logging import getLogger

if __name__ == '__main__':
	logger = getLogger(__name__)

	repo = DataBaseRepository()
	user_id = 100
	group_id = 1
	try:
		user_names = repo.get_users(group_id, gender_id)
	except Exception as e:
		"""
		ここにどうような処理を書くべき?
		"""
		logger.error(f'Failed to get user_name of group_id:{group_id} and gender_id:{gender_id}')
		raise(e)

	if len(user_names) == 0 {
		logger.warn('user_name size is zero!')
	}

これで、エラー対応が楽になるはず。。。

まとめ

要約すると
「エラー対応がしやすいように、付加要素的なエラーログを意識しましょう」
以上。

何か、ご意見やご指摘あれば、何卒コメント欄にご記入いただけますと幸いです。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?