Чому важливо перевіряти значення, що повертаються функцією?

Мені дуже захотілося поділитися досвідом і я хотів би поговорити про те, чому важливо перевіряти значення, що повертаються функцією. В якості прикладу візьмемо python і ctypes. Деякий час тому я зіткнувся з досить цікавим багом суть якого зводилася до того, що при запуску скрипта на Linux-системі були неправильні дані, але не було трэйсбэка, а на Windows-системі відразу ж отримували трэйсбэк. Дослідження коду показало, що винні були некоректні дані дати приходять в функцію strptime(). Тепер, давайте подивимося на приклад роботи з функцією strptime() у пітоні.

Під Windows ми можемо використовувати функцію strptime() з модуля datetime

Приклад з правильною датою:
from datetime import datetime

date_str = "30-10-2016 16:18"
format_str = "%d-%m-%Y %H:%M"

dt = datetime.strptime(date_str, format_str)

print repr(str(dt))

Ось що ми побачимо в цьому випадку:
2016-10-30 16:18:00
Якщо в коді вище ми замінимо рядок дати на некоректну:
date_str = «30-10-2016 16:fhadjkfh»
то побачимо наступний висновок:
File "E:\Python27\lib\_strptime.py", line 325, in _strptime
(data_string, format))
ValueError: time data '30-10-2016 16:fhadjkfh' does not match format '%d-%m-%Y %H:%M'

При використанні Linux, ми можемо так само використовувати функцію strptime() імпортуючи її з бібліотеки libc

Детальніше про функції strptime() в Сі краще всього почитати тут. Я ж тільки зазначу, що в даному випадку параметри дати будуть зберігатися в наступну структуру:
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};

Ось як може виглядати в пітоні використання функції strptime() при роботі з модулем ctypes:
from ctypes import *

libc = CDLL('libc.so.6')

class TM(Structure):
_fields_ = [
("tm_sec", c_int),
("tm_min", c_int),
("tm_hour", c_int),
("tm_mday", c_int),
("tm_mon", c_int),
("tm_year", c_int),
("tm_wday", c_int),
("tm_yday", c_int),
("tm_isdst", c_int)
]

tm_struct = TM()

for field_name, field_type in tm_struct._fields_:
print("{}: {}".format(field_name, getattr(tm_struct, field_name)))

strptime = libc.strptime
strptime.restype = c_char_p

date_str = "30-10-2016 16:18"
format_str = "%d-%m-%Y %H:%M"

rez = strptime(date_str, format_str, pointer(tm_struct))

print("######")
for field_name, field_type in tm_struct._fields_:
print("{}: {}".format(field_name, getattr(tm_struct, field_name)))

print "strptime returned: %s" % repr(rez)

І ми побачимо наступний висновок
tm_sec: 0
tm_min: 0
tm_hour: 0
tm_mday: 0
tm_mon: 0
tm_year: 0
tm_wday: 0
tm_yday: 0
tm_isdst: 0
######
tm_sec: 0
tm_min: 18
tm_hour: 16
tm_mday: 30
tm_mon: 9
tm_year: 116
tm_wday: 0
tm_yday: 303
tm_isdst: 0
strptime returned: "

Тут важливо зазначити, що поля об'єкта tm_struct буду ініціалізується нулями, а значенням повернула функція strptime() буде порожній рядок.
Якщо ж у коді вище ми замінимо рядок дати на некоректну:
date_str = «30-10-2016fahdkjfa 16:18»
то ми побачимо наступний висновок(для стислості я прибрав друк значень полів об'єкта tm_struct після його створення):
tm_sec: 0
tm_min: 0
tm_hour: 0
tm_mday: 30
tm_mon: 9
tm_year: 116
tm_wday: 0
tm_yday: 0
tm_isdst: 0
strptime returned: None

Тут можна побачити, що в разі некоректної дати в об'єкті tm_struct зміняться лише ті поля, які вдалося розпізнати в рядку дати до некоректних даних, а інші поля залишаться з нульовими значеннями. А сама функція strptime() поверне значення
None
. При цьому ніяких трэйсбэков ми не отримаємо. Ось тому важливо бути уважнішими і перевіряти значення, що повертається функцією.
Правильним варіантом виклику тут може бути, наприклад, такий:
# Так як і " None відповідають у пітоні False, то потрібно перевіряти саме на None
if strptime(date_str, format_str, pointer(tm_struct)) is None:
raise ValueError("datestring `{}` does not match expected format `{}`".format(date_str, format_str))

Тепер давайте уявимо, наприклад, що у нас є власний агрегатор розкладів чого-небудь. І при некоректному коді такий баг може бути помічений лише користувачем, у випадку якщо він побачить різницю між тим розкладом, яке показує наш агрегатор і розкладом на сайті, звідки ми його отримували.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.