Колеоптерология в IT, или Как избавиться от багов в своем коде

Всем привет, я Максим Усатенко, .NET разработчик в компании TELEMART.UA, и за несколько лет работы я стал автором десятков багов, которые в большей или меньшей степени негативно сказались на развитии компании.

Не хочу давать советы из серии: «Покройте 150% кода unit тестами, и будет вам счастье». Unit тесты  это вообще тема для отдельной книги. Разные компании имеют о них абсолютно противоречивые мнения, начиная от «Без них никуда» и заканчивая «Зачем тратить время на такую ерунду». Предпочитаю оставить этот вопрос открытым, с учетом бюджета, конкретного проекта или определенных бизнес-требований. Я же сделаю упор на человеческий фактор и определенные взгляды на процесс разработки  это именно то, что может пересмотреть каждый разработчик, не увеличивая бюджет проекта и не привязываясь к конкретным требованиям заказчика.

Эта статья будет полезна разработчикам, которые хотят уменьшить количество багов в своем коде или просто почерпнуть для себя новые подходы в написании ПО. Далее я приведу 7 кейсов из своего личного опыта, которые не только уменьшат количество ошибок на проде, но и улучшат чистоту и качество вашего кода.

Программная ошибка

Для начала давайте разберемся, что такое баг, или, как его правильно называть, «программная ошибка».

Программная ошибка (жарг. «баг») означает ошибку в программе или в системе, из-за которой программа выдает неожиданное поведение и, как следствие, результат. Большинство программных ошибок возникают из-за ошибок, допущенных разработчиками программы в её исходном коде либо в её дизайне.

Обращаю ваше внимание на то, что большинство ошибок возникает именно по вине разработчиков, а значит, в первую очередь это наша зона ответственности. К сожалению, баги от нас никогда не денутся, как бы мы ни старались. Писать код без багов невозможно, и человеческий фактор никто не отменял. В наших силах  только уменьшить их количество и снизить критичность. Важно понимать, что к ошибкам надо относиться серьезно, а не как к чему-то обыденному: «Кинь в unsprint tasks, и через полгодика исправим».

Достаточно вспомнить историю с ракетоносителем Ариан-5 в 1996, когда всего одна строчка кода уничтожила 1 ракету и 4 спутника по исследованию магнитосферы земли, с суммарным убытком в полмиллиарда долларов.

Не торопись

Торопиться и стараться сделать задачу быстрее — плохо. Сколько бы денег за работу ты ни брал, выполнить задачу и быстро, и качественно не получится. Я глубоко убежден, что лучше делать чуть медленнее и вдумчивее, чем после тратить время на поиск багов и фиксы.

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

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

Вот фразы, которые являются всадниками апокалипсиса в разработке ПО:

  • «Сейчас быстренько пофикшу, и сразу на прод зальем».
  • «До конца рабочего дня осталось совсем немного, правка маленькая, не будем терять времени и сразу выкатим».
  • «Нам надо успеть доделать 4 задачи и 7 багов до завтрашнего релиза, без регрессии обойдемся, вроде ничего сломаться не должно».

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

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

Null Reference

Около 30% всех багов — это Null Reference или Null Pointer (в разных языках  разное название, но суть одна). Кто-то где-то когда-то не предусмотрел значение NULL для поля, и теперь это вылилось в баг, возможно, очень серьезный и критичный. Вывод? Старайся всегда предусмотреть значение NULL настолько скрупулезно, насколько это возможно. Даже если сейчас кажется, что значение NULL невозможно, через год после тебя на проект придет другой разработчик, изменятся бизнес-правила, и уже никто не вспомнит про место в коде, где ты не предусмотрел NULL.

Также хочу отметить важность валидации данных перед вставкой в БД. Любая хорошо спроектированная реляционная база должна иметь определенный уровень нормализации (например, стандартный третий). Все эти уровни нормализации подразумевают наличие FOREIGN KEY, UNIQUE KEY, PRIMARY KEY и других элементов, поддерживающих целостность данных. Вставка несуществующего внешнего ключа приведет к ошибке, а это именно то, чего мы хотим избежать. Рекомендую перед любой CRUD операцией стараться проверять целостность данных.

Проверяй выполненную задачу сам

Хочешь мало багов? Проверяй код, чтоб их не было! Звучит, конечно, очень логично и понятно, но это факт, и это работает. Особый плюс этого подхода  в том, что тебе со своей стороны разработки могут быть виднее некоторые нюансы. Проверять нужно хотя бы минимальный положительный тест-кейс. Раньше я, как и многие, думал, что для этого и нужны QA, пусть отрабатывают свой хлеб. Теперь же мое мнение кардинально изменилось. Во-первых, это уменьшает количество багов, которые прилетают на QA, а во-вторых, это правило хорошего тона: отдать на тестирование то, что, как минимум, работает.

Минимально работающий функционал для меня — это:

UI — все открывается, нажимается, визуально все соответствует описанию задачи.

Новый API метод — дергается через Postman, отрабатывает основной положительный тест-кейс.

Новый SQL скрипт — скрипт отработал на тестовой базе корректно.

Также не стоит забывать просматривать все изменения по файлам перед каждым коммитом. Возможно, свежим взглядом вы увидите ошибку в своем коде либо же случайное исправление соседнего метода класса. Хочу привести пример: я случайно поставил точку с запятой в код, который не должен был менять. Тогда я не проверял изменения и не обратил внимания на лишнюю правку перед коммитом. Этот баг особо примечателен тем, что не ломает сервис, что можно легко отловить на этапе CI/CD, а попросту меняет поведение метода:

Вместо создания заказа на front-end улетала бизнес-ошибка. Думаю, не надо говорить, насколько важен метод CreateOrder для интернет-магазина. Этот мелкий, но очень критичный баг можно было бы легко отловить на этапе коммита, просто просмотрев изменения файлов в гите.

Не делай задачи одновременно

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

Раньше мне казалось: делать несколько задач одновременно  это быстро и практично. Экономлю время и при этом быстрее закрываю большой объем поставленных задач. На одном мониторе фикшу баг, на втором, в этом же сервисе, в другой ветке реализовываю фичу. Все классно до тех пор, пока тебя не отвлекут на пару минут, и в итоге в ветке бага я пилю фичу, а в ветке фичи исправляю баг. Как итог: потраченное время и нервы. Из плюсов разве что могу отметить новые скилы работы с гитом, ведь когда путаешь ветки, волей-неволей осваиваешь cherry pick и commit reverse.

Не доверяй сторонним API

API бывают разные: хорошие, плохие, интуитивные и не очень, документированные и абсолютно не соответствующие заявленному. Всех их объединяет одно: в любой момент времени они могут начать работать не так, как ожидаешь, и это надо предусмотреть. Простой интеграции с сервисом и успешных Unit-тестов недостаточно, чтобы считать его надежным. Я вообще полагаю, что рассчитывать на сторонний сервис рискованно, даже на сервис мирового масштаба. Достаточно вспомнить Google Indentity Service, который лег на полчаса в этом году, повалив за собой половину мировых компаний, завязанных на аутентификации гугла.

В моей практике было очень много багов, связанных с поломкой стороннего сервиса. Я интегрировался с «Новой Почтой», Portmone, LiqPay, MeestExpress, Monobank и другими украинскими компаниями, и все они рано или поздно выходили из строя на небольшой промежуток времени. Особо хочу выделить один сервис (не хочу тыкать пальцем), который отправлял в ответе вместо «OK» — «0K» (во втором случае это ноль), и это еще нужно спарсить и обработать. Рекомендую относиться к внешнему сервису как врач к пациенту в клинике для душевнобольных: общаться с уважением, но всегда входить к нему в палату с двумя санитарами и смирительной рубашкой наготове.

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

Помогай тестировщику

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

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

Отдыхай во время работы

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

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

Вывод: ради максимальной производительности нужно делать перерывы во время работы.

Итоги

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

В завершение хочу отметить, что эти кейсы помогают лично мне, и не факт, что они пригодятся вам. Твердо убежден, что каждому под силу проанализировать свои ошибки и найти определенные закономерности действий, предшествовавших им. Эти действия-триггеры багов  истинное зло IT современности, их надо искоренять раз и навсегда. Теодор Рузвельт говорил: «Никогда не ошибается тот, кто ничего не делает».

👍НравитсяПонравилось11
В избранноеВ избранном9
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

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

Точно, чем проще код — тем лучше. Жаль, фронтедщикам не понять :)

Жаль, фронтедщикам не понять :)

Що саме? Простий код можна написати складно, а складний — просто.

Есть методологии и языки программирования, в которых багов просто на взлете на два порядка меньше

Например? Мне на ум приходит Scala, где по умолчанию пишется компактный thread safe код.

В моїй голові складається наступна градація популярних мов якщо їх розглядати в контексті багозахисту:

-100: JS — найгірший варіант
— динамічна типізація => напорядок більше багів повязаних з типізацією
— відсутність базової бібліотеки => куча різних рішень що можу конфліктувати один з одним
— занадто проста мова => реалізація складних речей потребують багато зусиль

-10: TypeScript — покращення за рахунок типізації
— типізація працює тільки в рантаймі => при компіляції все добре, але немає гарантії навіть у простих випадках
— відсутність базової бібліотеки => куча різних рішень що можу конфліктувати один з одним

-5: Python, PHP(поправте якщо не правий працював з ними менше 40 годин) — покращення за рахунок базової бібліотекию Інші проблеми залишаються
— типізація працює тільки під час компіляції => при «компіляції» все добре, але немає гарантії навіть у простих випадках

0: Go, Java — покращеня за рахунок типізації
— залишаються проблеми з null

5: Kotlin, Scala, F# - вбудований захист від null
— захист від null все ж таки не ідеальний

10: С# - покращення за рахунок того що розробники можуть власноруч писати аналізатори для своїх бібліотек. Тим самим відслідковуючи не правильне їх використання підчас компіляції
— захист від null все ж таки не ідеальний

TypeScript — покращення за рахунок типізації
— типізація працює тільки в рантаймі

AOT же!

Ааа.. Так, але не так) AOT TypeScript-а дуже просто зламать. Наприклад, робиш реквест на сервер, отримуєш json, парсиш його. Якщо проперті зовсім не співпадають — то нічого не ламається, до тих пір поки не спробуєш достать звідти інформацію. Навіть адекватного способу перевірить що все норм не існує, оскільки, інформація про тип в рантаймі відсутня.

В мовах з статичною типізацією є рефлексія яка дозволяє, в цьому випадку, перевірити і сказати «Хей, тут якась фігня прийшла. Напевно, не варто продовжувати»

interface Reply {
    message: string;
}

const obj: Reply = JSON.parse(`{ "message": "Ви плутаєте компіляцію з виконанням, якщо бажаєте прокинути типи в рантайм візміть io-ts наприклад" }`);
console.log(obj.message);
interface Reply {
message: string;
}

const obj: Reply = JSON.parse(`{ «error»: «Я про цей випадок. Що робити з ним?» }`);
console.log(obj.message);

ви в жодній мові не будете створювати обьекти зі строки:) JSON.parse(`{ «error»: «Я про цей випадок. Що робити з ним?» }`);

Що робити з ним?

io-ts же

ви в жодній мові не будете створювати обьекти зі строки:) JSON.parse(`{ «error»: «Я про цей випадок. Що робити з ним?» }`);

Ви будете створювати обєкти з респонсу від сервера, а він якраз строка

io-ts

Вибачають не помітив що ви про нього писали. Приклад розірвало прямо по середені тексту «io-ts» і я його пропустив.

Зараз дивлюсь і в теорії повинно починить цю ситуацію. Єдине що цим ніхто не буде користуватися масово поки воно не попаде в базову біблеотеку. А так цікава штука, здвигає TypeScript в сторону Go, Java в тому рейтингу.

то я до того що це дуже синтетичний прикад для відображення «недосконалості» тайпскрипту, бо JSON це про міжпроцесорну передачу даних, тут більше питання про валідацію вхідних даних ніж про вивід типів, для мене така поведінка нормальна адже тайпскріпт не має рантайму і було б не добре зробити вивід типу а перевірку ні бо прийти може що завгодно

цим ніхто не буде користуватися масово поки воно не попаде в базову біблеотеку. А так цікава штука, здвигає TypeScript в сторону Go, Java в тому рейтингу.

було б непогано запозичити вже готові рішення :)

Я там скопіпастив не оттуда для TypeScript. Повинно бути

типізація працює тільки під час компіляції

як в Python з PHP

напорядок більше багів повязаних з типізацією

Я так зрозумів, що тут трапився нульпойнтерексепшин в голові у розробника, який звик все типізувати. В JS немає багів, які пов’язані з типізацією. Є проблеми з розумінням, як цим вірно користуватися.

Було б дуже цікаво подивитися чи почитати де розказують як вірно цим всім користуватися

А самостійно дійти до вірних висновків не виходить? Вам навіть розробники мови дали підказку у назві, що це скриптова мова, до неї треба підходити відповідно.

Замість того, щоб думати про структури даних, треба перейматися про сценарії та функціональну модель. Коли я всім джавістам або дотнетчикам кажу, що їм треба переставати думати про конкретні об’єкти та починати думати про потоки даних, в них миттєво виникає нульпойнтерексепшин. Бо в їхньому світові потік даних може бути тільки з чітко детермінованих об’єктів. Крок в сторону — розстріл. В потоці будь-яких даних типізація важлива? Так отож.

А самостійно дійти до вірних висновків не виходить? Вам навіть розробники мови дали підказку у назві, що це скриптова мова, до неї треба підходити відповідно.

Можливо ця логіка мала б який сенс в 2000-х роках. Зараз це уже дуууууже далеко від істини. На JS будуть дуже складні застосунки — це уже не просто скриптова мова.

Бо в їхньому світові потік даних може бути тільки з чітко детермінованих об’єктів.

Якщо ви пишете застосунок і вам важливо щоб він не розвалився при майбутьньому рефакторингі, то статична типазація дає дуже багато плюсів.

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

В потоці будь-яких даних типізація важлива?

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

Можливо ця логіка мала б який сенс в 2000-х роках. Зараз це уже дуууууже далеко від істини.

Не так далеко, як вам здається. Це все ще скриптова мова. На Сі теж можна писати великі застосунки, але це не ставить його в один ряд з іншими мовами.

Якщо ви пишете застосунок і вам важливо щоб він не розвалився при майбутьньому рефакторингі, то статична типазація дає дуже багато плюсів.

Це міф. Статична типізація вас обмежує, ви перестаєте думати про абстрактні речі та всі ваші думки будуть про правильний опис структури даних. Система стає крихкою, легко ламається, важко розширюється, важко модифікується, вимагає тестування, щоб переконатися, що все ок, тощо.

Про рефакторінг. Якщо ваша система вимагає постійного рефакторінгу, варто замислитися над тим, чи не зробили ви помилку в дизайні архітектури, що вам щось постійно треба міняти. Я, наприклад, не боюся змінювати свій код. Він побудований за принципом бінарного функціонування: або працює, або ні. Ситуації, коли працює, але погано, не буває в принципі. Тому що приміняються абсолютно інша декомпозиція та патерни в розробці, сильно відмінні від тих, що зараз хайпове та популярне на ринку. І відсутність типізації ніяк не впливає на якість продуктів.

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

Кому треба бути впевненому? Програмісту? Ви будуєте систему, в яких всі двері мають чіткий сілует, по якому пускають тільки людей з параметрами, що дозволяють їм пройти через ці двері. Такий собі Прокрустов світ. Але в реальності ми бачимо прямокутники (або щось схоже на це). Чому? Тому що визначення правильності параметрів того, хто входить в двері приймається вже всередені, коли людина перетнула їх. Замість того, щоб будувати коридори з прямокутними дверима, ви будуєте лабіринти з унікальними проємами, в яких проходження об’єкта з входу на вихід — цілий квест, як для тих, хто проходить його, так й для тих, хто будує ці лабіринти.

В реальності ви завжди будете визначати, що перед вами: зображення з цифрою, просто наявність об’єктів необхідної кількості, аудиосигнал з потрібною інформацією, тощо. А потім ви вже берете в розрахунок ту абстракцію, яку ви змапили з сигналом зовнішнього світу, та проводите роботу. Так працює ваш мозок. Якщо ваш код буде імітувати роботу мозку та завжди визначати, що саме прийшло на вхід, то ви побудуєте дійсно універсальний, fail safe та fault tolerant код. Ваша система стане простішою, а не складнішою.

Система стає крихкою, легко ламається, важко розширюється, важко модифікується, вимагає тестування, щоб переконатися, що все ок, тощо.

Ем... Це якраз все це я бачив в великих проектах написаних на js. Крок в сторону і нічого не зламалось, тестуємо нічого оне зламалось, заливаємо на прод, проходе декілька днів і вийявляється що зламалось там де ніхто не міг представить. А все через те що зникла одна пропертя... І так знову, і знову, і знову. Подібні помилки просто неможливі при статичній типізації.

Якщо ваша система вимагає постійного рефакторінгу, варто замислитися над тим, чи не зробили ви помилку в дизайні архітектури, що вам щось постійно треба міняти.

Якщо система розробляється більше 5-х років, то її початкові вимоги завжди будуть сутево відрізнятися від теперішних. На цьому етапі рефакторингу не уникнути. У мене складається враження, що ви ніколи не працювали на одному проекті більше року, оскільки, повністю ігноруєте зовсім очивидні речі прикриваючить тим, що там все просто і нічого бояться, а хто вважає по іншому самі собі буратини і так і не познали дзен.

Він побудований за принципом бінарного функціонування: або працює, або ні. Ситуації, коли працює, але погано, не буває в принципі.

Круто вам... ***к-хуяк і в продакшин..

Далі пішла якась несуразиця...

А все через те що зникла одна пропертя...

Ви тільки-йно описали проекти, які розроблялися людьми, які підходили до JS не як до скриптової мови. Написали класичний моноліт, де є висока пов’язаність між компонентами, та тепер плачуться, що все працює погано.

Окей, давайте спробуємо разом вирішити проблему зникнення властивості. Один з варіантів — статична типізація. Наслідки — втрата гнучкості системи та купа ручної роботи, тести-шмести та все таке. Які ще є варіанти? Їх безліч. Але спочатку треба визначитися, що саме ми візьмемо за основу нашого рішення. Один з варіантів — гарантування, що вхідні параметри є обов’язковими. Другий варіант — толерація помилок в цілому. Є ще інші, але то дуже довго.

При гарантуванні вхідних параметрів можна використати статичну типізацію, а можна валідацію, чи трохи складніше — визначення стану (поєднання валідації типу та значення). З першого погляду статична типізація виглядає значно дешевшою. Але з точки зору готовності до модифікацій та унеможливлення помилок при змінах в інших частинах системи, останній варіант значно кращий.

Якщо ми кажемо про толерацію помилок, то тут все ще цікавіше. Ваша система мусить сприймати помилку як норму. Не ті дані — ок. Не той формат — теж не привід панікувати та падати з нульпойнтер ексепшинами. Ви можете думати, що все поламається та не буде працювати, бо нічого нікому не відомо, але в системах, які толерують помилки в принципі весь код робочий. Єдине, що вам треба навчити систему — це на 100% виконувати ваші задачі.

Якщо система розробляється більше 5-х років, то її початкові вимоги завжди будуть сутево відрізнятися від теперішних.

Бізнес-вимоги до системи завжди однакові протягом всього періоду її існування.

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

Це хибне враження. У вас явно виражена професійна деформація під впливом однієї мови. Ви вважаєте, що світ .Net єдино правильний та не містить ніяких помилок за замовчуванням, бо мільйони мух не можуть помилятися, а тим паче головний Мух у вигляді MS, який й створив .Net. Ви ігноруєте реальність та обмежуєте себе. Світ набагато складніший та різноманітніший.

Далі пішла якась несуразиця...

От бачите, очевидні для мене речі для вас несуразиця. Стадія «заперечення».

Написали класичний моноліт, де є висока пов’язаність між компонентами, та тепер плачуться, що все працює погано.

По вашому стор данні із якого використовуються в 5 компоненах це уже моноліт?

Ви можете думати, що все поламається та не буде працювати, бо нічого нікому не відомо, але в системах, які толерують помилки в принципі весь код робочий.

Якщо розглядати з точки зору компілятора, то так код робочий, але ми ж тут зібралися не компілятор ублажать? З точки зору бізнеса код не робочий — він не виконує свої задачі для яких був написаний.

Бізнес-вимоги до системи завжди однакові протягом всього періоду її існування.

Ви точно не працювали над великими системами.

Ви ігноруєте реальність та обмежуєте себе. Світ набагато складніший та різноманітніший.

Я в постійному пошуку і постійно пробую нові мови програмування та підходи. Довгий час працював з Java, Kotlin, JavaScript. Маю уяву про Python, Golang, F#. Зараз працюю із C#, TypeScript/JavaScript. Мені є з чим порівнювати саме тому JavaScript отримав −100 в тому рейтингу.

Ви ігноруєте реальність та обмежуєте себе.

Це якраз ваш випадок. Ви ігноруєте той факт що був створений TypeScript, який кращий у всіх можливих вимірах. З цим згодні всі сучасні фреймфорки React, Angular, Vue, котрі перейшли на TypeScript. Крім того з часом, сам JS почав копіювати фічі TypeScript. Але ж ні, вам це не потрібно, то все від лукавого...

По вашому стор данні із якого використовуються в 5 компоненах це уже моноліт?

Ви тут дві коми забули поставити. П’ять разів перечитував, поки збагнув, що ви тут запитуєте. Справа не в тому де саме використовуються дані. Справа в тому, яким саме чином. Якщо у вас запит даних через певний протокол йде, який універсальний для всіх компонентів системи, то це далеко від моноліту. А якщо навпростець через методи об’єкту, то тут сильнопов’язана вже архітектура з усіма наслідками.

З точки зору бізнеса код не робочий — він не виконує свої задачі для яких був написаний.

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

Ви точно не працювали над великими системами.

Ні, це шановний пан плутає, що саме є бізнес-вимогами, та називає цим терміном ТЗ. ТЗ може змінюватися з часом, але бізнес-вимоги які були, такі й залишаються: дешево швидко зробити продукт, який буде стабільним, гнучким, та автоматизовувати процеси сьогодення та майбутнього.

Мені є з чим порівнювати

Але ви порівнюєте свої навні звички з можливостями мов та принципово не хочете їх змінювати. Ви самі собі звужуєте світ.

Це якраз ваш випадок. Ви ігноруєте той факт що був створений TypeScript, який кращий у всіх можливих вимірах. З цим згодні всі сучасні фреймфорки React, Angular, Vue, котрі перейшли на TypeScript. Крім того з часом, сам JS почав копіювати фічі TypeScript. Але ж ні, вам це не потрібно, то все від лукавого...

Він кращий з якої точки зору? Якими були критерії оцінки якості? JS був самодостатньою мовою, який мав свої особливості та чудово виконував поставлені задачі. Потім, через його популярність, туди набігло лігвоінвалідів з обмеженним баченням світу, спробували писати в звичній манері, отримали купу проблем, та замість того, щоб сісти та подумати, яку саме перевагу дає динамічна типізація, вони почали, як Прокруст, обрізати JS, щоб їх мозок не закіпав зайвий раз. Ні в кого навіть думки не було, що динамічна типізація вимагає трохи інших підходів до написання коду, що архітектура може бути зовсім іншою, що рівень абстракцій буде принципово відрізнятися від «класичного». На JS можна писати мало, а робити багато. А всі чомусь хочуть писати багато, робити мало. Бо мільйони мух не можуть помилятися.

Мені здається, що мови, які з 5 балами є як мінімум не менш безпечними ніж та, що 10. Як мінімум тому, що вони з самого початку проектувались для покращенної безпеки. Друге питання чи тільки єдиний C# має sdk аналізатора (www.scala-lang.org/...​6/scalafix-scalameta.html www.nuget.org/...​es/FSharp.Analyzers.SDK/ Також я не впевнений, що в C# багато де пишуть крім коду, тестів ще і власні аналізатори (не беру до уваги вже готові статичні аналізатори)

Не знав що у Scala вони є. За F# догадувався, але ніколи не бачив прикладів з ним. В такому випадку їм можна добавити +5.

Також я не впевнений, що в C# багато де пишуть крім коду, тестів ще і власні аналізатори (не беру до уваги вже готові статичні аналізатори)

Ну наприклад, сам Microsoft пише для своїх продуктів таких як asp.net core, immutable collections. Багато серіалізаторів мають аналізатори які слідкують щоб не забули розтавить правильні атрибути. Я пишу їх для своїх біблеотек. Хоча в моєму випадку вони пишуться оскільки в самому C# відсутні деякі фічі і я їх дореалізовую.

Мені здається, що мови, які з 5 балами є як мінімум не менш безпечними ніж та, що 10.

Ідеальна мова буде мати 100 балів, тому вони і не так далеко від один одного)

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

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

Зальём в продакшен — тестировщиков там больше
©

Обижаешь! Ровно к первому числу ночью, потому что так хотелось руководству. Независимо от степени готовности, льём найтли билд.

Работая как аустраф на крупных ентерпрайзах я заметил одну удивительную специфику работы их программистов. Нас всегда учили, что программист должен в своем коде учесть все варианты: граничные эффекты, неправильные входные данные, деление на ноль... Мой код метода обычно начинается с проверки параметров, потом каждый вызов чужих методов оборачивается в try-catch: в отличии от Java в C# это не требуется — но если я знаю что метод может бросить специфическое исключение — я должен его поймать и, как минимум, записать в лог. Опять же: телеметрия не бывает избыточной: если в коде метода есть ветвления то в логе должно быть записано по какому пути пошло выполнение. Еще одно из «детских» правил, которому учат с самого начала: не давить исключения и пропускать их выше если мы не можем ничего исправить.
В коде продуктов топ ИТ компаний я вижу совсем другой подход: там не должно быть исключений! Метод должен быть сделан так, что бы он не бросал ошибок даже если ему передали не валидные параметры. Он может не делать ничего или вернуть пустое значение — но ни в коем случае не кинуть исключение и не вернуть null !
Почему? Да потому что там прорабатывают только позитивные сценарии! Пользователь ввел правильные данные — получил правильный результат. В этом есть «бизнес велью». Ввел неправильные данные — и что? Данные могут быть неправильными по сотне причин, выясниться это может не любом этапе (сумма получилась равной 0, а нам надо делить). И если правильных сценариев — десяток, то неправильных можно придумать сотни!
Бизнес просто не хочет тратить время на проработку вопросов что может пойти не так. Но при этом система ни в коем случае не должна бросать ошибку пользователю — потому что такая ошибка это явная проблема приложения. А вот если пользователь сделал ошибку — а в ответ тишина или пустое значение или просто перезагрузка страницы то он подумает что что-то СЛУЧАЙНО пошло не так и просто повторит попытку!
Если же пользователь пробует несколько раз и натыкается на ошибку — то он просто позвонит в сапорт. И там ему, например, помогут сначала ввести фамилию, а потом — имя, потому что они знают что в обратном порядке — падает ошибка. То есть бизнесу выгоднее держать консультантов, которые подскажут единственно правильный путь обойти ошибки, чем предусмотреть все сценарии и заплатить девелоперам за их реализацию!
Так что к вопросу как избавится от багов: если работаете с чужим кодом то всегда проверяйте и пере-проверяйте: на null, на 0, на пустую строку — и лепите ветвления. Ваш код должен выполняться только если данные 100% правильные и не должен никогда падать. Если что-то не так: то ваш код просто «обходит» — и дальше не ваши проблемы!
Потому что никто и никогда в больших приложениях не скажет вам что правильно делать если пришел null — если коду больше года то даже сам автор уже может не помнить. Поэтому основное правило ентерпрайза: единственно правильное поведение кода — это то как оно работало раньше, до ваших изменений.

А так как проверяется такое правильное поведение кода изначально написанными тестами, можно сделать вывод: разработка, управляемая тестами не_бесперспективна и должна иметь место быть. То бишь, пришли от заказчика требования — пишем тесты, а там дальше как-нибудь все сложится.
На любое изменение — тест ДО и тест ПОСЛЕ.

пришли от заказчика требования — пишем тесты
На любое изменение — тест ДО и тест ПОСЛЕ.

Добро пожаловать на медицинские проекты!

В коде продуктов топ ИТ компаний я вижу совсем другой подход: там не должно быть исключений! Метод должен быть сделан так, что бы он не бросал ошибок даже если ему передали не валидные параметры

 — мне кажется здесь как раз и неправильный подход, просто «слой» который видит пользователь может отлавливать все проблемы и отображать ему фразу «Произошла ошибка». Но в случае C# попытка писать «без исключений»... выглядеть будет ужасно. Просто есть разная зона ответственности — где твоя библиотека и твой класс, и не его «собачье дело» поглощать внутри исключения, а кто там «наверху» фронт-енд рисует, вот там и думает чтобы пользователь не умер от увиденного стека.

Подписаться на комментарии