Я довольно долго занимаюсь написание программ. Уже лет 20 точно. И только недавно осознал, как писать надежные программы. Это банально, но чтобы программа не содержала ошибок, надо рассмотреть все возможные входные данные и доказать что определенные свойства будут выполнены. Сейчас я так не делаю, я пишу программу и начинаю тестировать. Тестирование выявляет проблемы, которые я правлю и продолжаю тестирование. Поэтому я посредственный программист.

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

Но и в процессе работы, учиться хорошим практиками довольно тяжело. Большое количество софта имеет очень низкие требования к качеству. Часто нужно, чтобы оно хоть как-то работало, а люди уж смогут приспособиться и обходить ошибки в программе. Во всяком случае, я умудрился 15 лет писать программное обеспечение и ни разу не видел, чтобы проекты провалились из-за низкого качества. Но то моя специфика, возможно в космической, финансовой и медицинской сферах по другому. Хотя, насколько я знаю, доказательное программирование повсеместно заменяется более тщательным тестированием. Чем больше тестируешь, тем меньше багов останется. Главное, чтобы фиксы багов не плодили новых багов. А то бывают программисты у которых этот процесс не сходится, ну или очень медленно сходится.

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

А вот с NPE я так и не поборолся, они регулярно выскакивают в моем коде. Хотя кажется, чего уж проще. Можно расставить аннотации и заставить IDE проверять на возможное null значение.

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