Почему важно проверять значения возвращаемые функцией?

в 7:07, , рубрики: ctypes, date, python, Программирование, разработка

Мне очень захотелось поделиться опытом и я хотел бы поговорить о том, почему важно проверять значения возвращаемые функцией. В качестве примера возьмём 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:Python27lib_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))

Теперь давайте представим, например, что у нас есть собственный агрегатор расписаний чего-либо. И при некорректном коде такой баг может быть замечен только пользователем, в случае если он увидит разницу между тем расписанием, которое показывает наш агрегатор и расписанием на сайте, откуда мы его получали.

Автор: kvothe

Источник

Поделиться новостью

* - обязательные к заполнению поля