Эффективная переделка интерфейса базы данных

Anatoliy Fedorenko
Jul 14, 2020 · 5 min read
Эффективная переделка интерфейса базы данных.

Эта история о боли, муках и отрицании готовых решений. Она также об изменениях, которые улучшают читабельность кода и помогают команде разработчиков оставаться счастливыми. Главный герой — интерфейс, который помогает коду взаимодействовать с базой данных.

Дисклеймер: Если бы мы использовали в этом проекте различные ORM, такие как Gorm, скорее всего, мы бы не столкнулись с этой проблемой, но мы решили написать нашу реализацию именно так, и это создало проблему, а следовательно и этот пост.

Вот так выглядит огромный интерфейс БД с которым не возможно работать:

Огромный интерфейс БД с которым не возможно работать

Проблема этого интерфейса заключалась в его размере — более 130 методов в одном интерфейсе! Это много методов, и это не то, как должен выглядеть SOLIDный интерфейс. Более того, так как мы пишем на Go, мы должны знать (и применять) одну из поговорок Go, а именно:

Чем больше интерфейс, тем слабее абстракция. © Роб Пайк

Чем дальше мы разрабатывали проект, тем тяжелее становился этот интерфейс, и вскоре стало ясно, что для продолжения разработки с меньшим количеством ошибок, меньшим количеством времени, затрачиваемым на понимание кода, и большим комфортом, этот интерфейс следует реорганизовать. Мы как команда не могли использовать этот интерфейс гибко. Мы не могли сказать с первого взгляда, что он делает, так как он делает буквально всё. И это заставило меня начать его рефакторинг. Как раз этим процессом я и хочу поделиться с вами.

Это очень важно, поскольку работа с интерфейсами, абстракциями и рефакторингом без тестов, которые охватывают логику основного API, не приносит пользы. Я бы сказал, что это может сделать с точностью до наоборот — принести много проблем вашему проекту, так как каждое изменение, которое вы вносите в интерфейс, приводит к изменению более 20 файлов. И если у вас нет надежных тестов, есть большая вероятность, что вы что-то сломаете или создадите ошибки. Пожалуйста, будьте осторожны!

Я решил сосредоточиться на общих типах, которые есть в проекте. Немного подумав и просмотрев список функций я обрисовал как будет выглядеть будущий интерфейс в общих чертах. Вот что я придумал:

Итоговый желаемый интерфейс

Это позволило бы написать следующий код вместо ссылки на один из 130+ методов из интерфейса:

user, err := api.Storage.GetUserByID(ctx, userID)
err := api.Storage.DenyAgreement(ctx, agreement)
err := api.Storage.UpdateUserDeviceToken(ctx, model.UserDeviceToken{...})

После изменений методы выглядили бы следующим образом:

user, err := api.Storage.User().Get(ctx, userID)
err := api.Storage.Agreement().Deny(ctx, agreement)
err := api.Storage.User().UpdateDeviceToken(ctx, model.UserDeviceToken{...}

Как видите, этот интерфейс состоит из нескольких меньших интерфейсов, каждый из которых основан на определенных общих типах (пользователи, соглашения и т. д.). Чтение таких конструкций гораздо удобнее и практичнее, поскольку, если вам когда-нибудь понадобится что-то сделать с пользователем, вы знаете, где искать правильные методы, и не надо думать, существуют ли они вообще.

Наличие интерфейса с более чем 130 методами значительно усложняет процесс рефакторинга в стиле «раз и навсегда». Получается так много изменений, что каждый Merge Request превращается в изменения более 50 файлов. Поэтому следующим шагом должно быть постепенное разбиение интерфейса, один тип за другим и частая фиксация (коммиты) этих изменений для создания небольшого, понятного MRа и обеспечение того, чтобы все по-прежнему работало должным образом (помните шаг 0!). Для этого я сначала разбил интерфейс на несколько более мелких интерфейсов:

Постепенное изменение интерфейса по частям

Итак, я создал несколько под-интерфейсов и заявил, что мой интерфейс IStorage реализует их все. Это не сильно изменило код, но заложило важный подготовительный блок к тому, что я хотел сделать дальше, который по сути заменяет мои подчиненные интерфейсы на отдельные интерфейсы с их отдельными методами в стиле CRUD, добавляя недостающие методы и объединяя те, которые имеют один и тот же смысл. Я добавил новые структуры в качестве реализации этих интерфейсов и заменил старые методы на новые через функцию “найти-заменить” VSCode и постепенно заменил все функции.

Типы и функции нового интерефейса

В результате я изменил все под-интерфейсы IStorage на его интерфейсы и сделал все функции более интуитивными и понятными. Да, это заняло у меня некоторое время, но результат того стоит.

Шаг 3: Чистка

Массовое редактирование с помощью команды find -> replace all помогает сэкономить время, но также создает некоторые побочные эффекты, в которых вы можете переименовать логи в коде, по которым в последствии будет очень сложно ориентироваться. Это то, что случилось со мной после рефакторинга.

Когда логгируется ошибка, мы получаем сообщение «не удалось обновить», что конечно не понятно без контекста. Да, в логах есть дополнительные параметры которые указывают, где именно произошла ошибка, но этого может быть недостаточно, чтобы определить что случилось, почему случилось и быстро среагировать. Итак, мой совет здесь заключается в том, чтобы продолжить рефакторинг и пересмотреть логи проекта и другие компоненты, на которые может повлиять рефакторинг.

Сторонние эффекты рефакторинга БД интерфейса

По ходу дела выяснилось, что мы используем много разных методов для примерно одного и того же действия (например, обновление различных полей таблиц различными методами). Это плохой код, и в будущем мы избежим этого анти-паттерна.

Кроме того, я обнаружил некоторые функции, которые не подходили для определенных мест в коде. Например, когда соглашения (agreements) сильно зависели от рефералок (referrals) при обновлении. Это не имело смысла после того, как я начал рефакторинг. Наличие этого «всё в одном» интерфейса позволило коду использовать такие конструкции, но рефакторинг показал, насколько это ужасно, и сделал жизнь с этим кодом невозможной. Надо переписать функции, чтобы создать более естественную, интуитивно понятную функциональность.

Да, такой рефакторинг добавляет код в проект. “Обмазываться” интерфейсами не всегда приятно, но этот подход дает нам больше возможностей для улучшения читабельности кода, удобства поддержки и простого проектирования. Я бы в любом случае сделал этот рефакторинг, хоть если не в начале развития проекта.

Заключение

Если у вас есть проект с очень тяжелым интерфейсом базы данных, и вы видите, что он продолжает расти, рассмотрите возможность рефакторинга как можно скорее, так как со временем всё будет только хуже.

При рефакторинге всегда сначала проверьте тесты, затем решите, как должен выглядеть интерфейс в итоге, и придите к этому решению через множество частичных изменений, по одному компоненту за раз. Это сохранит нервы вам и вашим коллегам и уменьшит вероятность появления новых багов! После рефакторинга больших интерфейсов всегда проверяйте «побочные эффекты».

Если вы когда-либо сталкивались с той же проблемой, пожалуйста, поделитесь своими решениями в комментариях, так как мне действительно интересно, как другие разработчики решают подобные проблемы. Также, пожалуйста, поделитесь своими мыслями о том, что я мог (и все еще могу) сделать, чтобы сделать этот грешный интерфейс еще лучше. Любые отзывы приветствуются!

Mad Devs — блог об IT

Engineering your growth. Mad Devs is the team behind large scalable projects, globally.

Mad Devs — блог об IT

Mad Devs is a Cambridge-headquartered IT company developing enterprise-level software solutions for finance, transportation & logistics, security, edtech, and advertising industries. For more information about us, please browse our website: https://maddevs.io/

Anatoliy Fedorenko

Written by

Golang Backend Developer at Mad Devs / Bot Father

Mad Devs — блог об IT

Mad Devs is a Cambridge-headquartered IT company developing enterprise-level software solutions for finance, transportation & logistics, security, edtech, and advertising industries. For more information about us, please browse our website: https://maddevs.io/

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store