Императивный и декларативный подход
Ревьюил сейчас финальные проекты ребят по курсу и много говорил о декларативном и императивном подходе в программировании. Понимаете разницу?
На самом деле очень много можно уложить в применение этих подходов.
Мне нравится пример с бутербродом. Когда вы говорите товарищу — дружище, так-так, бросай всё, поднимайся, иди-ка на кухню, левой рукой открывай холодильник, а правой доставай колбасу, затем закрывай холодос, бери нож, отрезай кусок от колбасы, потом от батона, клади колбасу поверх батона и тащи это всё получившееся довольному мне — то вот он императивный подход. Товарищ скорее всего такого не одобрит. Да и вы сами тоже опупеете это всё в таком виде произносить.
А вот фраза вроде «сваргань-ка мне бутерброд, пжалста» — это уже декларативный подход. Товарищ сам вполне разберётся, как обращаться с холодильником, батоном и колбасой. Ему не пришлось выслушивать дурацкую тираду, вам не пришлось её произносить, окружающим не захотелось вас поколотить за занудство. Прекрасно.
Декларативный подход — вы декларируете то, что вам надо, не погружаетесь в детали реализации.
Если переносить это на программирование, то, скажем, задачу распарсить некий запрос на заголовок, содержащий только первую строку, и оставшееся тело запроса, можно разными способами. Можно так — даже не вынося эту логику парсинга в отдельную функцию, разместя её в составе какой-то большей функции обработки запроса:
def process_client_request(request: str): """какой-то длинный код тут...""" """teletype.in не умеет в комменты с символа диеза...""" delimeter_position = request.find("\n") header = request[:delimeter_position] body = request[delimeter_position:] """и потом тоже какой-то длинный код..."""
Грустно? Ну невесело. Много сложного кода. Какие-то индексы находятся, слайсы по ним ищутся. Там, где сложный код — больше вероятность ошибок, сложнее поддерживать код, дольше, дороже, неприятнее.
Когда мы с find
ищем позицию в строке какой-то подстроки — то непонятно, зачем мы это делаем. Может, мы хотим проверить вхождение подстроки в строку. Может, мы хотим обрезать строку. Может, хотим разбить её. Может ещё что-то. Чёрт его знает, надо читать окружающий код, чтобы понять нас. Это сложно. Надо думать. Думать — тяжело.
def process_client_request(request: str): """какой-то длинный код тут...""" header, body = request.split("\n", 1) """и потом тоже какой-то длинный код..."""
Потому что split
это понятный метод разбивки строки на подстроки, это декларативный подход, скрывающий реализацию. Это — декларативно и просто, и думать не надо, чтобы понять, что в коде происходит.
А можно ещё проще, ещё декларативнее:
def parse_request(request: str) -> tuple[str, str]: return request.split("\n", 1) def process_client_request(request: str): """какой-то длинный код тут...""" header, body = parse_request(request) """и потом тоже какой-то длинный код..."""
Кода стало больше — но код стал лучше.
- Читать стало проще, приятнее, понятнее. Читая строку
header, body = parse_request(request)
, мы понимаем, что тут происходит. Тут происходит парсинг запроса, а как именно он там этот парсинг происходит сие есть дело десятое, при желании можно перейти в вызываемую функцию и почитать. - Ещё один плюс — возможность переиспользовать
parse_request
ещё где-то. - И ещё один — если задача изменится и нам надо будет парсить запрос как-то иначе, нам не надо будет искать этот код где-то в дебрях другого кода, он лежит себе отдельно в своей отдельной маленькой великолепной функции.
А если ещё dataclass или NamedTuple вместо tuple
использовать для распаршенного запроса — так вообще будет песня:)
Занимаясь написанием кода, задумывайтесь о том, какой код вы сейчас пишете — императивный или декларативный?
Как сделать этот код более декларативным? Как упростить его?