Программисты должны быть параноиками.

  • “Я дважды проверил код”
  • “Код прошел тесты”
  • “Ревьюер одобрил мой код”
“Мой код верен?”

Писать правильный код сложно, а проверить его корректность невозможно. Вот несколько причин, почему:

  • Универсальность: Даже если ваш код работает правильно один раз, будет ли он работать так во всех случаях, на всех машинах, во всех ситуациях?
  • Ложноположительные результаты: Неудачные тесты указывают на наличие ошибок, но пройденные тесты не обещают их отсутствия.
  • Отсутствие уверенности: Вы могли бы написать формальное доказательство корректности вашего кода, но теперь вы должны задаться вопросом, верно ли это доказательство. Вам нужно будет подтвердить доказательство. Эта цепочка проверки доказательств никогда не закончится.
Глупо добиваться абсолютной уверенности в правильности своего кода. Ошибка может скрываться в зависимостях, которые вы никогда не найдете. Тем не менее, не стоит отчаиваться. Мы все еще можем снизить риск возникновения ошибок, добиваясь глубокого понимания кода и добросовестно работая с ним.

Абстракции


Что такое «более глубокое понимание»? Давайте остановимся на одном аспекте понимания кода, актуальном для программистов: абстракции.

Абстракции — это…

  • ментальные модели того, как именно работает код
  • когда мы трактуем сущность A так, как если бы она была сущностью B
  • метафорически …
    • всегда хочется уложить информацию в голове как можно компактнее
    • нужно уметь увидеть лес за деревьями
  • постоянно используется в повседневной жизни
Слово «абстракция» многозначное. В программировании понимаются такие слои кода, которые скрывают сложность. В этом посте речь пойдет только об абстракциях в когнитивном смысле.

Примеры абстракции:

  • Мы относимся к своим банковским вкладам так, как будто банк просто хранит эти деньги для нас.

    • В действительности банк не просто хранит деньги, которые мы кладем на депозит. Большую часть денег, которые люди кладут на депозит, он отдает в долг/инвестирует. Наши деньги не лежат без дела большой кучей в хранилище..
    • Абстракция работает, потому что банки все еще держат в кассе достаточно наличности, чтобы справиться с большинством операций по снятию денег.
  • Мы относимся ко времени так, будто оно течёт одинаково быстро для всех.
    • Релятивистское замедление времени слегка изменяет ход времени для каждого человека/объекта в зависимости от скорости и силы тяжести, под которой человек находится.
    • Спутники GPS, вращающиеся вокруг Земли, автоматически корректируют свои часы на ~38 микросекунд в день, чтобы учесть замедление времени (Источник)
    • Абстракция работает, потому что эффект замедления времени слишком мал, чтобы его заметить, если только вы не занимаетесь чрезвычайно точным проектированием.
___________________________________________________________________________________

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

  • Зажигание заводит автомобиль
  • Акселератор заставляет автомобиль двигаться
  • Тормоз заставляет машину останавливаться
  • Колеса приводят автомобиль в движение
  • Автомобилю требуется бензин/дизель
Зная эту абстракцию, нет необходимости разбираться во внутреннем устройстве автомобильных двигателей. У большинства водителей есть только эти прикладные знания об автомобилях, и они могут доехать туда, куда им нужно.

Когда мы используем язык программирования, он предоставляет абстракции, которые позволяют нам управлять компьютерами, не вполне понимая их внутреннее устройство.

  • Основные фичи языка (такие как циклы, if-условия, функции, операторы и выражения) — все это абстракции, которые скрывают:

    • Детали аппаратного уровня: Инструкции процессора, регистры, флаги и детали, характерные для архитектуры процессора, …
    • Детали на уровне ОС: управление стеком вызовов, управление памятью, …
  • Портируемость: Языки абстрагируют нас от необходимости заботиться о различиях между разными машинами.
    • Любая скомпилированная Java-программа (например, jar-файл) должна работать на любой машине, на которой установлена среда выполнения Java (т.е. JVM)..
    • Скрипт Python должен выполняться на любой машине с интерпретатором Python..
    • Программа на языке C должна компилироваться и выполняться на любой машине, если на ней есть компилятор C..

Абстракции отказывают


К сожалению, абстракции не работают.

  • Языковых абстракций недостаточно, если вы стремитесь повысить производительность кода. Чтобы ускорить работу кода, необходимо знать детали аппаратного уровня и уровня ОС.
  • Перенос программ, имеющих внешние зависимости, такие как динамические библиотеки или сетевые требования, не так прост. Их нельзя просто скопировать на другую машину и запустить. Требуется дополнительная настройка, а для нее требуются знания.
  • Автовладельцы, которые знают лишь самый минимум, могут оказаться в ситуации, когда машина сломалась. Если водитель не будет регулярно менять смазку/масло в своем автомобиле, он сократит срок службы двигателя.
Абстракция водителя хорошо работает в краткосрочной перспективе (для одной поездки), но терпит крах в долгосрочной перспективе (многие годы). Джоэл Спольски назвал такие неудачные абстракции " дырявыми" и вывел закон дырявых абстракций:

Все нетривиальные абстракции в той или иной степени являются дырявыми.

Что аналогично аксиоме из статистики:

Все модели ошибочны, но некоторые из них полезны.

Когда мы пишем код, мы постоянно используем «дырявые» абстракции. Вот несколько примеров:

  • Сборка мусора освобождает нас от необходимости заботиться об управлении памятью (если только мы не уделяем особого внимания задержкам)
  • Умные указатели C++ обеспечивают безопасность памяти (при условии, что вы не храните в ней сырые указатели)
  • Хэш-таблицы быстры, потому что в них выполняется O(1) операций (но массивы быстрее при меньших размерах).
  • Передача по ссылке быстрее, чем передача по значению (за исключением случаев исключения копирования и значений, которые помещаются в регистры процессора, например целые числа)
К счастью, многие «дырявые» абстракции при сбое приводят к краху вашего кода и сразу проявляются, поэтому их легко устранить. Однако некоторые из них могут приводить к неопределенному поведению или снижению производительности, что сложнее выявить и исправить.

Нажмите X, чтобы усомниться


Итак, если абстракции могут быть проблематичными, то стоит ли пытаться понять тему без абстракций (чтобы узнать автомобили такими, какие они есть на самом деле)? Нет. Когда вы копаете под абстракциями, вы просто находите еще больше абстракций. Это черепахи до самого низу.

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

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

Доверяй, но проверяй


Программист должен придерживаться политики «доверяй, но проверяй».

Вот несколько примеров:

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

Остерегайтесь неизвестных


Самая страшная эпистемологическая проблема для программистов — это «неизвестное неизвестное”.

Существуют …

  • вещи, которые вы знаете (т.е. „известные“)
  • то, что вы знаете, но не знаете (»известные неизвестные"
  • вещи, о которых вы даже не подозреваете, что они вам неизвестны («неизвестные неизвестные»)
Эти неизвестные — корень неудач абстракции (и причина, по которой программисты никогда не могут точно предсказать, сколько времени займет проект).

Возможно, вы никогда не слышали о …
  • Дезинфекции пользовательского ввода
    • Если вы применяете пользовательские строки в SQL-запросах, ваш сервис может быть взломан с помощью SQL-воздействий..
  • Символьных кодировках
    • Любые текстовые данные, которые обрабатывает ваш код, должны использовать кодировку символов (например, ASCII, UTF-8, UTF-32 и т. д.), которую ожидает/поддерживает ваш код.
    • Произвольный доступ к символу в текстовом буфере может занимать постоянное время (для ASCII) или линейное время (для UTF-8) в зависимости от кодировки символа…
    • Вы можете вывести непонятные символы, если попытаетесь прочитать текстовые данные, записанные в неправильной кодировке.
  • Размер кучи Java
    • Ваша программа может замедлиться из-за нехватки памяти кучи.
    • Вы могли бы решить эту проблему, если бы знали, что нужно настроить больший максимальный размер кучи для вашей Java-программы.
Если вы раньше не слышали об этих темах, то можете даже не подозревать, что попали в ловушки.

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

При работе с незнакомой платформой/языком/инструментом/библиотекой/технологией или их изучении:

  • Читайте больше документации, чем необходимый минимум
  • Смотрите видео
    • Презентации на конференции отличаются высочайшим качеством
  • Читайте посты в блогах
  • Читайте исходный код
  • Расширяйте понимание абстракций, с которыми вам придется работать
    • Узнайте о функциях, недавно добавленных в язык программирования, с которым вы работаете
    • Ознакомьтесь со всеми публичными функциями библиотек, а не только с теми, которыми вы пользуетесь.
    • Просмотрите все флаги в man-странице инструмента CLI.
  • Изучите абстракции хотя бы на уровень ниже, чем вам нужно.
    • Узнайте об оптимизациях вашего компилятора.
    • Если вы используете сервис, изучите платформу оркестрации (например, Kubernetes).
    • Если вы работаете на Java, изучите JVM.
    • Если вы работаете на Python, изучите интерпретатор Python.

Заключение


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

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

При этом реальные обстоятельства должны соотноситься с идеалами. Мы должны быть готовы понести некоторые краткосрочные издержки, чтобы выиграть время на тщательное обучение и проверку. Не только для того, чтобы писать правильный код, но и как часть нашего долгосрочного профессионального совершенствования.

Комментарии (6)


  1. iv_kingmaker
    12.07.2024 13:04
    +3

    Уже было.


  1. markshevchenko
    12.07.2024 13:04

    И всё-таки паранойя — это грустно. В пику паранойе предлагаю практиковать разумный пофигизм. Сделал ошибку — и фиг с ней!

    Иначе можно погрязнуть в чтении бесполезных блогов.


    1. doctorw
      12.07.2024 13:04
      +1

      Упал прод – и фиг с ним! :)


      1. markshevchenko
        12.07.2024 13:04

        Сразу какие-то ужасы... Ну, упал и упал. Даже у гугла прод падает.


        1. doctorw
          12.07.2024 13:04
          +2

          Вот хотите Вы оплатить какую-то покупку, а Ваш банк упал прямо в процессе оплаты. А покупка, скажем, не одну тысячу стоит. Всё ещё даже, или уже неприятно?


          1. markshevchenko
            12.07.2024 13:04

            Было и такое в моей жизни. Жив, здоров, и всё ещё не подвержен паранойе.

            И, более, того, всё ещё убеждён, что инженер имеет право на ошибки. А паранойя это плохо, говорят даже, что это болезнь.

            Так что лучше быть здоровым и богатым, чем нездоровым и с постоянными мыслями о пропадающих деньгах.