Почему код, который мы пишем — убивает людей?

В современном мире, когда программное обеспечение проникает в каждый уголок нашей жизни, качество кода становится критически важным. Мы, разработчики, каждый день создаем новые продукты, улучшаем старые и автоматизируем различные процессы. Но осознаем ли мы всегда глубокую ответственность, которую это несет? Наши решения определяют функционирование всего: от банковских систем до медицинских устройств. Сколько раз мы задавались вопросом о потенциальных последствиях нашего кода? В этой статье я хочу подчеркнуть важность написания качественного кода и рассмотреть потенциальные риски его пренебрежения.
Производительность или качество?
Мир IT-разработки сложен и многогранен. Особенно когда речь идет о сроках и производительности. В гонке за выполнением проектов и разработкой новых фич, легко потерять из виду главное: качество продукта. Но что такое «качественный код»?
- Конвейер разработки: Сегодня большинство компаний внедряют методологии быстрой разработки, которые направлены на ускорение процессов. Это, безусловно, эффективно с экономической точки зрения. Но при этом возрастает риск ошибок, потому что мы постоянно находимся под давлением. Недостаточно времени на анализ, планирование и, главное, тестирование. Результат? Код, который может работать, но не всегда корректно или эффективно.
- Саморефлексия разработчика: Хороший код — это не только тот, который выполняет свою функцию. Это код, который легко читать, масштабировать, который эффективен и безопасен. Но как часто мы, разработчики, задаем себе вопросы о качестве своего продукта? Сколько раз мы возвращаемся к своему коду, чтобы оптимизировать его, даже если он уже «работает»?
- Ответственность перед пользователем: Наши пользователи доверяют нам. Они полагаются на наш продукт, на наш код. Они ожидают, что все будет работать без сбоев и ошибок. Каждый раз, когда мы жертвуем качеством ради скорости, мы рискуем потерять это доверие.
В завершение: производительность — это важно. Но не менее важно помнить, что мы создаем продукты для людей. И каждый уголок кода, который мы пишем, влияет на их опыт использования, безопасность и удовлетворенность.
Значимость тестирования
Тестирование – это не просто шаг в процессе разработки; это крайне важное звено, которое обеспечивает качество и надежность кода. Рассмотрим несколько ключевых аспектов этой стадии.
- Не все тесты созданы равными: Многие разработчики воспринимают тесты как нечто формальное. «Покрытие кода тестами на 90%? Отлично! — А можем сделать 100?». Но покрытие кода не всегда коррелирует с качеством. Важнее фокусироваться на реальных сценариях использования и критически важных функциях, чем на цифре в отчете.
- Регрессионное тестирование: С каждым обновлением кода возрастает риск нарушить уже существующую функциональность. Регрессионное тестирование позволяет удостовериться, что новые изменения не внесли нежелательных последствий. К сожалению, это то, что многие продукты упускают из вида в погоне за сроками.
- Проактивное тестирование: Хорошо, когда разработчик не просто реагирует на ошибки, но и предугадывает потенциальные проблемы, создавая тестовые сценарии даже перед тем, как начать написание кода. Но будем честны, сколько из нас так делают?
Каждый код, который мы пишем, может влиять на жизни людей – независимо от того, создаем ли мы медицинский софт или простое мобильное приложение. Это не просто строка кода; это ответственность. Тестирование не является дополнительной опцией или шагом, который можно пропустить; это неотъемлемая часть создания качественного продукта.
Реальные последствия недостаточного качества кода
Многие разработчики ошибочно полагают, что их обязанности завершаются, как только функция исполняется или ошибки исправлены. Однако в реальности даже незначительный промах в кодировании может привести к катастрофическим результатам.
Пример из медицинской сферы:
Представим систему для медицинских учреждений, предназначенную для отслеживания назначенных пациентам препаратов. Все кажется простым: врач назначает препарат, система фиксирует его, а пациенту предоставляются рекомендации по дозировке и режиму приема. Но что если из-за ошибки в коде дозировка лекарства, которая была указана в формате «10.0 мг», изменилась на «100 мг» после обновления программы из-за пропавшей десятичной точки? Такая ошибка может привести к тому, что пациент примет в десять раз больше лекарства, чем ему было назначено, что грозит серьезными осложнениями или даже смертью.
И многие могут подумать: «Это же очевидная ошибка. Пациент наверняка проконсультируется с врачом или свяжется с медицинским центром перед тем, как принять такую дозу». И хотя в большинстве случаев это может быть так, всегда найдется человек, который просто подумает, что врач изменил рекомендованную дозу или даже не обратит на это внимания и примет лекарство, руководствуясь ошибочной инструкцией. Таким образом, даже одна маленькая ошибка в коде может иметь глубокие и трагические последствия для конкретного индивида.
Многие ошибки в коде не всегда очевидны или проявляются непосредственно. Некоторые могут стать заметными только при определенных условиях или при взаимодействии с другими системами. Именно поэтому крайне важно подходить к процессу разработки ответственно, обращая внимание на детали и проводя глубокое тестирование. Недоразумения, поспешность или пренебрежительное отношение в разработке могут привести к потере жизни, ущербу репутации или большим финансовым потерям.
В заключение: каждая написанная нами строка кода имеет значение. Будь то масштабный медицинский проект или небольшое приложение для локального предприятия, последствия нашей работы ощущаются, и мы должны осознавать свою ответственность.
Заключение
В эпоху цифровизации, когда многие аспекты нашей жизни тесно связаны с программным обеспечением, ответственность перед конечным пользователем стоит на первом месте для каждого разработчика. Каждая строка кода и каждое решение в процессе разработки могут оказать влияние на благосостояние и даже физическое здоровье людей.
Ответственность разработчика не заключается лишь в создании функционирующего продукта. Она также включает в себя гарантирование безопасности, надежности и корректности работы продукта в различных ситуациях и условиях.
С учетом этого, нам, разработчикам, следует осмыслить свои приоритеты: стоит ли действительно экономить на времени разработки или на тестировании, рискуя безопасностью пользователей? Не лучше ли иногда уделить дополнительное время проверкам, чтобы гарантировать высокое качество продукта?
Пользователи доверяют нам свои данные, задачи и, иногда, свою жизнь. Давайте оправдывать это доверие, внимательно относясь к качеству своей работы и осознавая последствия своих решений.
- кодирование
- тестирование
- безопасность
- ошибки программирования
- медицинское ПО
- ответственность разработчика
- регрессионное тестирование
- последствия ошибок
- качество продукта
- доверие пользователей
Почему код становится legacy?
Написание кода похоже на соединение двух точек. Ожидаемо, что самым простым путем будет нарисовать прямую линию между точками А и Б.

Для большинства простых случаев это работает, но вот в реальной жизни, кроме соединения точек, вам придется еще и обходить различные препятствия. Но в целом — тут тоже никакой проблемы, кроме необходимости немного поманеврировать.
Давайте увеличим количество препятствий на порядок. Линия становится все более извилистой.
Теперь давайте заставим эти препятствия двигаться. Медленно, но достаточно для того, чтобы вызвать проблемы с необходимостью переподключения точек. Это уже не такая простая прямая линия, и начинает выглядеть куда серьезнее.
А если мы заставим двигаться не только препятствия, но и сами точки? Вдобавок убедимся, что эти точки не приклеены к линиям, и вам придется следить за ними, чтобы они оставались соединенными. Начинает немного бесить?
Но и это еще не всё. Представьте, что мы знаем лишь приблизительное положение точек, и единственный способ узнать его — запрашивать их местоположение каждые 5 минут? Походит на безумие, или еще нет?

Усложним еще немного. Допустим, мы знаем только приблизительное положение точек.
Вот это уже в значительной степени похоже на то, как происходит разработка в реальном мире.
Обсуждение
В контексте этой метафоры точки — это функциональные требования к тому, что должно делать программное обеспечение. Замечательно, когда мы точно знаем, правильно ли наш код соединяет их. Однако на практике эти требования меняются (отсюда и движение точек), и нам приходится соответствующим образом обновлять наш код. Тем не менее, мы не всегда можем легко определить, правильно ли мы соединили точки — для этого надо проводить тщательное тестирование.
Препятствия могут быть ограничениями в технологии, производительности, программировании, различными процессами и всем остальным, что может встать на нашем пути. Всегда есть что-то, с чем мы как решатели проблем уже привыкли сталкиваться.
Само собой, при такой динамической ситуации код, который изначально был написан с наилучшими намерениями и практиками, превращается в уродливую кривую, свидетельствующую о мучениях, через которые пришлось пройти разработчику, чтобы заставить его работать.
Такой код известен как legacy — код, который очень сложно изменить без полного краха и который обходится так же дорого, как и полная перепись с нуля.
Да, с классической точки зрения, легаси — это старый код, который нужно поддерживать как internet explorer, или код, написанный человеком, который уже не работает в вашей компании.
Но в современном обиходе этот термин более широкий и обозначает плохой ломкий код.
Как запутанная пряжа, устаревший код обычно проявляет высокую связность между логическими блоками, которую так же сложно размотать.
Отсюда возникает вопрос, можно ли что-то сделать, чтобы избежать устаревания нашего кода.
Как решить эту проблему?
Частично она решается с помощью так называемых шаблонов проектирования — точечных решений, которые мы можем собрать вместе, подобно конструктору Лего, чтобы сделать процесс быстрее и более контролируемым.
Но шаблонов проектирования недостаточно. Гораздо важнее знать, что мы быстро движемся в правильном направлении, нежели правильно двигаемся в неправильном. И да, в мире полно устаревшего кода, который следует лучшим шаблонам проектирования. Фактически, чрезмерное использование шаблонов проектирования сверх необходимого называется «оверинжинерингом».
Мы должны как можно быстрее узнавать, где находятся точки из нашего примера, и для этого нам нужен автоматизированный мгновенный способ получения обратной связи. В этом помогает тестирование вашего кода. Да, не каждый тест подходит, но тестирование по стратегии черного ящика специально проверяет именно точки, а не какие-то части по пути.
В общем, чтобы решить проблему постоянно меняющегося положения точек и препятствий, нам нужно выполнить два условия:

- Как можно быстрее узнавать, что мы быстро движемся в правильном направлении (обратная связь о правильности в виде черного ящика тестов)
- Использовать заранее продуманные решения для быстрого и контролируемого построения пути (шаблоны проектирования).
Что в итоге
Эти два пункта выше очень просто звучат, но их не так-то просто достигнуть в контексте разработки пользовательского интерфейса, чем мы и попытаемся заняться в этом цикле статей.
Однако основное внимание будет уделено первому аспекту (немедленной обратной связи), так как именно он часто оказывается наиболее болезненным, в то время как дизайн-паттерны относительно хорошо изучены и известны, поэтому они будут упомянуты только кратко.
Почему разработка пользовательского интерфейса особенно склонна к Legacy?
Используя метафору с линией из примера выше, мы пришли к определению того, что такое legacy-код:
Legacy-код — это код, который очень сложно изменить, ничего не сломав в процессе, и его переписывание обходится так же дорого, как написание с нуля.
Такой код появляется, когда у нас есть сложные и постоянно меняющиеся ограничения и требования, обратная связь о корректности задерживается, а объем кодовой базы — постоянно растет.
И это очень похоже на разработку пользовательского интерфейса в общем понимании этого процесса. И веб-разработка пользовательского интерфейса не является исключением. Больше скажу — возможно, нет другой области, где требования меняются так же часто, как в областях, связанных с пользовательским интерфейсом.
Дисклеймер: Все приведенные ниже примеры являются выдуманными, упрощенными и нужны только для иллюстрации — любое сходство с реальными людьми случайно.
От идеи до legacy
Greenfield — проект одного человека, создаваемый с нуля
Есть такие счастливые люди, которые начинают работать над проектом вообще с нуля. Пожалуй, это самый крутой этап для разработчика.

Когда вы получаете требования, идея того, что необходимо создать, обычно ясна. Список задач свежий и хорошо организован, все понятно, а дизайн по большей части почти готов.
Итак, вы начинаете работать над проектом, настраиваете конфигурации и прочее. Но как только этот этап остается позади, игра начинается — теперь вы создаете.
Пока вы создаете свои первые компоненты, вам четко заметен быстрый прогресс. Ещё бы — вам не нужны тесты, ведь вы можете перемещаться по всему приложению всего в пару кликов. Свой пользовательский интерфейс вы можете просматривать с помощью сервера разработчика или какой-то песочницы. Вашим приоритетом является выполнение требований, а не сохранение кода на будущее.
С некоторыми неудачами (мы же с вами в реальном мире живем) вы справляетесь. Всё сделано вовремя, все довольны.
Проект Brownfield
Более вероятен вариант, при котором вы окажетесь в другой ситуации и будете работать над чьим-то проектом «с нуля».

Вам будет гораздо сложнее понять мыслительный процесс человека, который создавал приложение до вас. Как он думал, что делал, зачем. Скорее всего, он не обращал много внимания на что-то, кроме требований, поэтому тестирования тут будет мало (если оно вообще будет). А логика будет куда более запутанной из-за упрощения разработки.
Теперь, получив требования, вы сможете только догадываться, сколько времени потратите на реализацию.
Пока вы начинаете работать над проектом, вы в основном отлаживаетесь через код. Вы больше не можете кликать по пользовательскому интерфейсу и знать, где находится каждый элемент. По мере разработки вы стараетесь как можно меньше изменять исходную логику. Пошагово вы тесно связываете одну функцию с другой, пытаясь понять исходный код и закончить работу к концу спринта.
Может быть, у вас и не будет возможности надлежащим образом протестировать что-то, так как вы уже будете по уши загружены требованиями и отладкой.
Тем не менее, если вам повезет, вы начнете писать тесты для своего кода. Наверняка это будет уже история про тестирование белого ящика (структурное), то есть ваши тесты будет привязаны к реализации, а не станут тестировать общую логику с входными и выходными данными. А это станет проблемой.
Шаг за шагом, и кодовая база станет тем, что называется legacy.
Legacy-проект
И даже пример выше не такой печальный, как самая частая ситуация — работа над legacy-проектом в качестве разработчика.

Здесь почти нет надежды на рефакторинг устаревшей части. Цикл обратной связи для большинства приложений будет ощутимо задерживаться, а процесс покрытия приложения тестами всегда будет отставать и подрывать мотивацию.
Заключение
Разработка пользовательского интерфейса подвержена устареванию, потому что быстрая обратная связь и эффективные шаблоны проектирования, позволяющие обеспечить модульность, не включаются в проект на этапе его создания с нуля.
Далее в цикле:
- Так как же сделать пользовательский интерфейс легкотестируемым и легкоизменяемым?
- Decoupler MVU или как реализовать MVU архитектуру для UI приложений
Полезные ссылки
- Black Box Testing: An In-Depth Tutorial — Этот туториал на Guru99 предоставляет всестороннее руководство по тестированию черного ящика. В нем рассматриваются его методы, типы, приемы, преимущества и недостатки.
- Design patterns — Подробное руководство по шаблонам проектирования программного обеспечения. Каждый шаблон подробно объясняется с примерами, чтобы лучше понять, когда и где их использовать.
- SOLID Principles: Explanation and examples — Этот пост на FreeCodeCamp разбирает принципы SOLID в понятной форме с большим количеством примеров.
- Understanding Legacy Code: Этот сайт — отличный источник информации для понимания legacy-кода и стратегий эффективной работы с ним. Поможет вам ориентироваться и улучшать legacy-системы.
- Greenfield vs Brownfield: В этой статье на LinkedIn Giorgi Bastos подробно рассматривает концепции проектов greenfield и brownfield. Автор дает отличное представление о различиях, преимуществах и вызовах обоих подходов.
- Greenfield vs Brownfield in Software Development: Synoptek предоставляет подробное сравнение между разработкой программного обеспечения greenfield и brownfield, дополнительно улучшая понимание этих концепций и их влияния на разработку пользовательского интерфейса.
- Advantages and Disadvantages of White Box Testing: На JavaTpoint опубликовано сбалансированное представление о тестировании белого ящика и подробное обсуждение его преимуществ и ограничений.
- White Box Testing: Pros and Cons: Segue Technologies предлагает другую точку зрения на тестирование белого ящика, подробно описывая его плюсы и минусы. Эта статья поможет вам понять, почему тестирование белого ящика может быть частью проблемы при поддержке кода пользовательского интерфейса.
- философия программирования
- програмирование
- легаси
- legacy
- поддержка кода
- UI
- Блог компании билайн
- Программирование
- Совершенный код
- Дизайн
Почему код не проходит тест #3? (Solved)
In the test here on Playground, you can print your matches and look, what happend with a few different inputs. Then you can see where is problem and try to find out, how can it be corrected. Потому что это, как идти по темной дороге. Otherwise it is groping on a dark way. https://code.sololearn.com/cx1tfbykB3pp/?ref=app
4th Nov 2020, 4:41 PM
Often have questions like this?
Learn more efficiently, for free:
Introduction to Python 7.1M learners
Introduction to Java 4.7M learners
Introduction to C 1.5M learners
Introduction to HTML 7.5M learners
Почему код на Python работает быстрее в функции?
При изучении Python новичкам часто приходится сталкиваться с тем, что код, который они пишут в функциях, работает быстрее, чем тот же самый код, но без обертки в виде функции. Рассмотрим пример:
def example(): for i in range(10**8): pass example()
Вышеуказанный код может работать быстрее, чем следующий:
for i in range(10**8): pass
Почему это происходит?
Прежде всего, стоит уточнить, что Python — это интерпретируемый язык программирования. Это означает, что каждая строка кода исполняется поочередно. При этом интерпретатор Python использует специальные объекты для представления различных элементов кода, таких как функции, циклы и так далее.
Когда Python исполняет код внутри функции, он создает специальный объект функции, который содержит весь код этой функции. Если код функции не изменяется, то Python может кешировать этот объект и использовать его при каждом вызове функции. Это может существенно ускорить исполнение кода.
В случае с кодом, который не обернут в функцию, Python должен каждый раз заново создавать объекты для каждой строки кода. Это занимает больше времени и ресурсов, что приводит к медленной работе кода.
Кроме того, Python имеет оптимизации для локальных переменных внутри функций. Эти оптимизации делают доступ к локальным переменным быстрее, чем к глобальным.
В заключение можно сказать, что использование функций в Python не только помогает делать код более структурированным и понятным, но и может ускорить его работу.