Речь пойдет о тайной, сугубо анонимной организации, следы которой начал замечать еще в 2018-ом, работая в Яндексе. О целях и мотивах организации можно только догадываться: некоторые считают это кибер-луддизмом, другие — техно-анархизмом. Ясно одно: организация существует, ее члены уничтожают кодовые базы десятилетиями, и говорить об этом не принято.

Риск, который я на себя беру, публикуя статью, велик, но молчать больше не могу. Поделюсь приемами организации, которые удалось идентифицировать, пока работал в мобильной разработке и в бэкенде.
В серии статей будет 3 части, подписывайтесь на телеграм-канал, чтобы не пропустить:
Тактика работы с кодовой базой (эта статья);
Тактика работы с архитектурой;
Тактика работы с коллективом.
Оставляю за собой свободу написать больше статей на тему, ведь постоянно изобретаются все более изощренные техники саботажа.
Если же данная статья окажется последней — значит, до меня добрались.
План статьи
Обсудим, как ревнители упомянутого выше братства маскируют вредоносный код, прячут проблемы на code review, создают абстракции с экспоненциальным ростом сложности.
Содержание:
Конструирование ловушек в кодовой базе;
Когнитивное истощение или диалектика говнокода;
Подрыв производительности.
Примеры кода даны на Kotlin и за небольшим исключением будут понятны разработчикам «мейнстримовых» языков, вроде Java, Go, C++, TypeScript, Swift, Dart, Python.
Я не претендую на полноту изложения, но постарался изложить то, что доставило больше всего боли.
Конструирование ловушек в кодовой базе
Поборники известной подпольной организации редко вносят критические ошибки сами, предпочитая умело расставлять ловушки, чтобы формально злодеяние оказалось на совести другого разработчика.
Ловушка через «destructuring»
Чем как ни «дестракчерингом» — читай разрушением — пользоваться, когда нужно разрушить кодовую базу? Это и концептуально, и двусмысленно, и невинно одновременно. Давайте рассмотрим пример: создается data class
c полями, порядок которых в будущем захочется поменять.
data class User(
val age: Int,
val id: Ing, // как будто бы id должен быть первым?
val name: String,
val surname: String,
)
И где-то в другом файле пишется:
val (_, id) = getUser()
Если какой-то разработчик поменяет поля age
и id
местами в декларации класса, что в общем-то безобидно, компилятор ничего не заметит и не подскажет о проблеме. В худшем случае какое-то время вместо id
будет использоваться возраст.
Как это решать?
Можно завести отдельный класс для Age
, тогда изменение порядка полей приведет к ошибке компиляции (Age
не пройдет туда, где ожидается Int
):
value class UserAge(val age: Int)
Можно не использовать destructuring вовсе.
Ловушка через мета-программирование
Когда нужно добавить какую-то логику, мы идем в ожидаемое место. Бэкендеры за бизнес-логикой пойдут в handler и/или service, мобильные разработчики - в Interactor
или UseCase
. В redux
-архитектуре бизнес-логика находится в функции reduce
, а в TEA - update
. Если речь про логику отрисовки деталей View
, ее бы искали в реализации кастомной вьюхи. В общем, всегда ясно, куда идти.
Соответственно, с позиции технического терроризма нужно прятать логику так, чтобы о ней и не знали вовсе.
Лучший пример, с которым сталкивался, — использование AspectJ в мобильном приложении. Не очень хочу называть имена, но намекну, что статей про AOP в Яндексе не так уж и много. Суть в том, что где-то в мета-файлах, куда никто никогда не заглядывает, помещаются инструкции, привязанные к текущей формации кода в проекте. Пишется что-то такое: найди класс с именем “X“ и вставь после метода “У“ в месте выхода из функции следующий код.

Работать с кодовой базой, зараженной Аспектами, становится больно, ведь изменение некоторых функций может сказаться непредсказуемым образом. Приходится держать в памяти код аспектов: на какие строчки и какие методы они завязаны, что и где добавляют. Безумно хорошо аспекты «синергируют» с наследованием, когда неявная логика прячется за уровень наследования.
Возможно, я был бы менее уверен в критике подхода, если бы не оказался жертвой технического терроризма, и не наблюдал, как от Аспектов страдал каждый коллега. Что примечательно, агент саботажа покинул команду и уже через неделю насаждал хворь в другой, соседний проект.
Продают технологию под соусом поддержания чистоты: «код не будет замусорен аналитикой, логами, security-проверками». Кажется логично, что мета-задачи размещаются в мета-файлы, но на мой вкус, проблем больше:
Неявная и невидимая (в случае наследования) логика.
Дополнительный шаг компиляции.
Дополнительная технология, которая нигде больше не пригодится и с которой нужно разбираться, ставить плагины для чтения файлов с аспектами.
Судя по всему, к этому пришел не только я, вот как выглядит частота гугления AspectJ с 2004 года:

Другой пример из другого языка программирования: макросы в Clojure. Макросы позволяют добавлять все что угодно в язык. Я хорошо знаком с core языка, писал на Clojure год за деньги и еще года 3 в свободное время. Но когда попадается кодовая база, усыпанная большим количеством макросов, все покрывается туманом. Выше я перечислял проблемы использования аспектов, все те же минусы актуальны и здесь (кроме, пожалуй, невидимой логики).
Ловушка через неявную связность
Пишем в базу (в кеш, в переменную) в одном месте, читаем в другом — вместо того, чтобы передать данные явно (чисто).
suspend fun addUserToGroup(context: Context) {
// много логики тут
db.addUser(context) // данные о пользователе берутся из контекста
notifyUsers(context)
// еще логика
}
suspend fun notifyUsers(context) {
val users = db.getUsers(context: Context)
ws.notifyUsers(users)
}
Первая цель саботажа — дождаться, пока кто-то поменяет строчки местами:
suspend fun addUserToGroup(context: Context) {
// ...
notifyUsers(context)
// логика сломается, и нужному юзеру никогда не придет notification
db.addUser(context)
}
Вторая цель саботажа — создать гонку (race condition). Пользователю (из контекста) должна прийти нотификация, а для этого функции addUser
и getUser
должны быть в одной транзакции.
Обеих проблем можно было бы избежать, сделав функцию notifyUsers
«чище», принимая на вход users
:
suspend fun addUserToGroup(users: List<Users>) {
// ...
val users = transactionResult { con ->
db.addUser(con, context)
db.getUsers(context.groupId)
}
val notification = notifyUsers(users)
// ...
}
Если вам кажется, что пример надуманный, то в оправдание скажу, что видел подобное не единожды. Вместо addUser
могла быть другая модификация базы, например, deleteUser
.
Ловушка через нарушение гайдлайнов и ожиданий
Еще один прием — играть на ожиданиях. Пример в Kotlin: скрывать бизнес-логику и ветвление внутри функции apply
. Функция сама по себе предназначена для конфигурации объекта, и разработчик, видя ее, не подумает, что туда попадет бизнес-логика, не относящаяся к конфигурации объекта.
fun handleProducts(request: Json) {
val products = parseJson(request).apply {
onProductRequestReceive(this)
}
service.handle(products)
retspond(HttpStatus.OK)
}
Разработчик пойдет искать и дописывать логику в service.handle(product)
и может продублировать код или внести багу. Да пусть хотя бы повозится какое-то время с «магическим» поведением — уже неплохо с точки зрения хитреца, установившего ловушку.
Когнитивное истощение или диалектика говнокода
Смысл активности в том, чтобы из каждой строчки выжимать дополнительную сложность. Разрушители кодовой базы терпеливы — их устраивает, что не сразу, но где-то на горизонте случится переход количественных изменений в качественные. Тогда кодовую базу будет поддерживать очень дорого, а порой и вовсе невозможно.
Когнитивное истощение: отрицание отрицания
Вместо того, чтобы писать прямое условие, всегда можно написать инверсное:
val enabled = isEnabled()
if (!enabled) {
// some other logic
} else {
// some logic
}
Да, это всего лишь добавляет на одну мыслительную операцию больше, но, во-первых, курочка по зёрнышку клюет, а во-вторых, оно легко масштабируется:
val disabled = isDisabled()
if (!disabled) {
// some logic
} else {
// some other logic
}
Тут уже нужно сделать две дополнительных мыслительных операции: одну на отрицание “!“, вторую на мысленный перевод disabled
в enabled
. Кроме того, созданием метода с сигнатурой fun isDisabled()
гарантируется, что во всей кодовой базе будут создаваться диалектические отрицания отрицания.
Нельзя отрицать, что ничего из отсутствующего никогда не понадобится разрушителям кодовых баз, об этом и продолжим.
Когнитивное истощение: неинтуитивные необязательные функции
Другой прием — использование функции с логикой, которую приходится каждый раз проверять. Желательно, чтобы логика была контринтуитивной, как в случае с импликацией:
infix fun Boolean.implies(other: Boolean): Boolean = !this || other
И использование:
val isRaining = false
val carryingUmbrella by lazy { getUmbrella() != null }
val ok = isRaining implies carryingUmbrella
Для кого-то логическая импликация — очевидная вещь. Можно давить на необразованность или синдром самозванца ревьюера, чтобы защитить PR. Цель же такого кода — заставить других разработчиков при встрече с implies
идти смотреть определение функции, потому что даже тот, кто знаком с концепцией, должен будет убедиться, что функцию реализована ожидаемо.
А теперь самое сладкое: только представьте, насколько хорошо можно комбинировать импликацию с отрицанием отрицания!
Попробуйте понять, есть ли ошибка в программе ниже. Должен ли user идти домой, чтобы не промокнуть, если учитываются две переменные: идет дождь, есть зонтик:
val shouldIGoHome = !noRaining implies !noUmbrella
И посмотрите, насколько проще читается:
val shouldIGoHome = if (isRaining) {
!carryingUmbrella()
} else {
false
}
Когнитивное истощение: go to
Казалось бы, все современные языки отказались от go to
, и после выхода статьи Дейкстры «Go To Statement Considered Harmful» (кстати, тут же могу порекомендовать и статью Go statement considered harmful) только самые ярые представители техно-анархизма сопротивлялись и продолжали апологию go to
. Движение было сильно, и сейчас вы можете найти злосчастный стейтмент и в JS, и в C++, хотя большинство гайдлайнов его запрещают.
Что нам более интересно, все популярные языки неявно поддерживают go to
.
Для демонстрации проблемы упростим логику приложения до простого стека выполнения программы:
+ Поток выполнения
+ Логика первого уровня
+ Логика второго уровня
+ Логика третьего уровня
- Завершение логики третьего уровня
- Завершение логики второго уровня
- Завершение логики первого уровня
- Завершение потока выполнения
На примере бэкенда это могло бы выглядеть так:
+ Цикл обработки запросов
+ Middleware start
+ Handler function start
+ Service function start
- Service function end
- Handler function end
- Middleware end
- Закрытие сервиса
Теперь давайте представим, что нужно доработать бизнес-логику: в случаях, когда в запросе присутствует флаг deferred
(«отложенный»), мы не будем запускать основную логику, а сделаем вместо этого deferredLogic
и вернем 202, а не 200. Изначально (упрощенный) код мог выглядеть как-то так (прошу, не обращайте внимание на «нейминг»):
class SomeService {
// some code here
fun someFunction(request: SomeRequest): Result {
// more logic here
return doLogicWithRequest()
}
sealed class Result {
class Done(): Result()
// more results
}
}
// где-то на уровен хэндлера есть код с pattern matching по SomeService.Result
// Когда Result.Done, возвращается 200.
Чтобы реализовать логику, можно было бы дописать ветвление в ожидаемом месте:
+ Цикл обработки запросов
+ Middleware start
+ Handler function start
+ Service function start (вся новая логика тут)
- Service function end
- Handler function end
- Middleware end
- Закрытие сервиса
class SomeService {
fun someFunction(request: SomeRequest): Result {
return if (request.deferred) {
doDeferredLogicWithRequest(request)
} else {
doLogicWithRequest(request)
}
}
fun doDeferredLogicWithRequest(request: SomeRequest): Result.Deferred {
// тут какай-то логика по отложенному запросу
}
sealed class Result {
class Done(): Result()
class Deferred(): Result()
// more results
}
}
Но это было бы слишком просто, предсказуемо, легко в поддержке, подходило бы как для unit-тестов, так и для интеграционных. Давайте взглянем на задачу с целью нанесения максимального урона кодовой базе.
+ Цикл обработки запросов
+ Middleware start
+ Handler function start
+ Service function start (часть логики тут)
+ DAO function start (часть логики тут)
- DAO function end
- Service function end
- Handler function end
- Middleware end (часть логики тут)
- Закрытие сервиса
class SomeService {
fun someFunction(request: SomeRequest): Result {
// обратите внимание, ничего не намекает, что
// поток выполнения может прерваться, и не видно, где он возобновится
if (request.deferred) {
dao.processDeferred(request) // <- хотя тут кидается ошибка!
}
return doLogicWithRequest(request)
}
// тут тоже ничего не намекает на новые варианты ответов
sealed class Result {
class Done(): Result()
// ...
}
}
class SomeDAO {
fun processDeferred(request: SomeRequest) {
// что-то пишем в базу
throw SomeDefferedException()
}
}
И где-то на просторах «Middleware»:
catch(e: SomeDefferedException) {
call.respond(202)
}
Обратите внимание, насколько это прекрасно: если ревьюер (который как правило занят другими задачами и торопится) посмотрит на каждый отдельный кусочек кода, все будет выглядеть приемлемо. Ну вызвали какой-то метод в сервисе. Добавили какой-то другой метод. Мало ли, понадобилось кинуть исключение. Добавилась какая-то обработка в Middleware.
Теперь представьте, что практически в каждом «хэндлере» присутствует неявная логика. Написание нового кода в предсказуемом месте может прерываться непредсказуемым образом через go to
(throw
). При вынесении кода на другой поток ошибка вообще потеряется.
Всегда ли исключения - плохо? Если речь о нормальном потоке выполнения, а не об ошибке, то да. Никогда не стоит реализовывать логику через исключения. Могу предположить, что адепты уничтожения корпораций вдохновляются книгами, вроде Effective Java, делая противоположное.
Use exceptions only for exceptional conditions. c. Effective Java
Когнитивное истощение: слепой try-catch
Если android-разработчик встретит код из листинга ниже, он сразу обнаружит, что Exception не обработается:
fun function() {
// ...
try {
this.post {
if (badCondition) throw BadException()
}
} catch(e: BadException) {
// какая-то обработка ошибок
}
// ...
}
Так как post
ставит блок кода в очередь, которая обработается вне рамок try-catch
. Чтобы реализовать «слепой try-catch», нужно значительно расширить его «скоуп». И возможно, в одном из мест выкинуть exception вне post
, чтобы ублажить ревьюера.
Гляньте пример, все станет ясно:
fun function() {
try {
// какой-то код здесь
if (badCondition) throw BadException()
// какой-то код здесь
newFunction() // <- вся хитрость тут!
} catch(e: BadException) {
// какая-то обработка ошибок
}
// ...
}
fun newFunction() {
// ...
this.post { // <- и вот он post, чтобы BadException не обработалось
if (anotherCondition) throw BadException()
}
}
Если на ревью спросят, зачем такой широкий «скоуп» try-catch
, всегда можно сказать, что Exception кидается несколько раз, зачем дублировать код? Надо DRY! Надо ли? в части про архитектуру в следующей статье об этом будет свой раздел.
Когнитивное истощение: скрытый источник данных
Обычно данные в базе данных появляются или в результате выполнения кода проекта (например, добавление юзера при регистрации), или в результате миграций. Можно включить третий источник — добавление данных от триггеров в базе. В любом случае, практически всегда можно разобраться: найти код, ответственный за добавление.
Эту особенность заметили разрушители, и я был свидетелем — если не сказать «потерпевшим» — последствий саботажа. На одном из тестовых стендов перестали проходить тесты, и я долго разбирался, почему данных не хватает. Первая гипотеза была связана с тем, что код случайно удалили, и я проверил историю релизов на полгода, выискивая, где оно потерялось. Оказалось, данные просто добавили руками.
Почему так лучше не делать? Скрипт о добавлении данных может затеряться, а на тестовых стендах может произойти чистка (запланированная или случайная). Могут добавиться новые стенды.
Файл миграции мог бы выступить в роли документации, поясняя, что данные статические. При необходимости раскатить сервис «с нуля» на новый стенд не нужно будет ничего делать руками дополнительно.
Когнитивное истощение: неидиоматичный код
С каким бы стеком вы ни работали, всегда ясно, как писать «правильно» — то есть как писать так,
как принято,
как пишут все,
как разработчики языков и фреймворков подразумевали написание кода.
Техно-анархисты зашли очень далеко и даже пишут книги с рекомендациями писать неидеоматично (Data-oriented programming). Автор рекомендует в статически типизированных языках, вроде Java и C#, использовать мапы вместо классов.
Вдохновившись этой книгой, пробовал писать в функциональном стиле flutter-приложение на Dart.
Dart is an object-oriented, class-based, garbage-collected language with C-style syntax. c. Wikipedia
Кто-то может возразить, что Dart поддерживает и функции высшего порядка, и иммутабельность, и стримы, и библиотеки с персистентными коллекциями есть. Но этого не хватает, и мне кажется, что попытка писать полностью функционально тормозит разработку на Dart раза в 3-4 в сравнении с дефолтным подходом (субъективно).
Также не стоит писать FP на Go или OOP на Clojure. Я зайду дальше и скажу, что на Clojure не стоит писать как на Haskell, используя моноиды и прочую теорию категорий. Clojure предлагает отличный набор инструментов для sequence abstraction, все остальное только мешает читать и поддерживать код.
Подрыв производительности
Код из под их пера подобен песочному замку, который не переживет очередного прилива.
Цитата неизвестного автора.
Маскировка проблем производительности через циклы
Прежде чем начать, сделаем небольшое отступление. Уверен, что каждый — даже самый маленький — понимает, что запрос к базе, развернутой на другой машине, значительно медленнее, чем запрос к хэшмапе (словарю) в том же потоке. Давайте оценим, насколько.
Данные получены из Графаны рабочего сервиса, в момент, когда я идентифицировал код агента хаоса. В конкретном примере поход в базу обходился в 2-4 миллисекунды.
Время запроса к мапе — 10-60 наносекунд. 10 из статьи «HashMap performance improvements in Java 8», 60 из личных замеров на своем стареньком маке.
Поскольку для человека наносекунды интуитивно не понятны, давайте переведем все на минуты. Вот что получается при грубом подсчете: если обращение к мапе — это 1 минута, то поход в базу — это примерно полгода. Разница колоссальная, как видите, и приверженцы техно-анархизма ее эксплуатируют.
Если обращение к мапе — это 1 минута, то поход в базу на другой машине — это примерно полгода.
Очень сложно «задедосить» сервис снаружи, но невероятно просто — изнутри. Надо только добавить запрос к базе в нескольких вложенных циклах. Вы спросите: как? ведь придется проходить code review! Я встречал неопытных анархистов, которые прямо вот так и писали:
for ...
for ...
for ...
for ...
db.pool.execute...
И этот код жил и хоронил сервер. Не преувеличиваю ни на цикл.
Выше — почерк неопытного агента анархии: его легко найти и устранить (я про код). Искусство же мастеров заключается в том, чтобы скрывать циклы, ловко манипулируя вниманием ревьюера. Давайте рассмотрим пример с виду безобидного кода, который пытается спрятать запрос к базе в трех вложенных циклах.
Первый файл — модельки данных:
// Названия продуктов в заказе
data class Order(val items: List<String>)
// какая-то информация по рекламе
data class AdInfo(val adds: List<String>)
В другом файле — работа с заказами. Главное, помещать функции из одного юз-кейса в разные файлы и перегружать внимание ревьюера разнообразием синтаксиса.
fun processOrder(data: Order) {
// Тут какой-то отвлекающий код, чтобы усыпить внимание.
// Анархист надеется, что цикл тут будет пропущен:
repeat(data.items.size, ::process)
// Еще какой-то код, отвлекающий внимание.
}
fun process(index: Int) {
val item = data.items[index]
val (detailInfo, success) = getProductDetails(item)
when(success) {
true -> processDetail(detailInfo)
// специально бросается general Exception,
// чтобы ревьюер мог зацепиться и проглядел реальную проблему
else -> throw Exception()
}
}
fun getProductDetails(item: String): Pair<List<String>, Boolean> = TODO()
fun getAdInfo(key: String): List<String> = TODO()
Третий файл — попытка спрятать цикл через создание интератора. Даже при беглом просмотре кода вы увидите for
, но если закамуфлировать итератор за typealias
, можно и проглядеть:
fun processDetail(details: List) {
// какой-то еще код ...
val detailProcessor = details.prepare()
detailProcessor.process()
// какой-то еще код ...
}
// Еще лучше было бы, если вынести код ниже в отдельный файл,
// чтобы спрятать информацию о листах и итераторах
typealias DetailsProcessor = ListIterator
fun List.prepare(): DetailsProcessor {
return listIterator()
}
fun ListIterator.process() {
while(this.hasNext()) {
val adInfo = getAdInfo(this.next())
adInfo.process()
}
}
Обратите внимание, process
не намекает на работу с базой, в отличие, например, от save
. А ниже рекурсия опять скрывает цикл. Тут важно постоянно использовать разные конструкции: repeat
, forEach
, for
, while
, (0..size)
, onEach
, рекурсию, do while
— это создает дополнительную когнитивную нагрузку.
fun AdInfo.process() {
// тут какой-то код ...
if (addInfo.adds.count() == 0) return
val info = adInfo.adds[0]
saveAds(info)
this.copy(adds = adInfo.adds.subList(1))
.process()
}
fun saveAds(add: String): Boolean {
db.pool.execute {
// sql inside 3 for loops
}
}
Ок, скажете вы, этот код абсурдный и никакое ревью не пройдет. Можно докопаться практически до каждой строчки. Дело в том, что кода будет больше, значительно больше. Там, где речь не идет о главном для анархиста (внутренняя DDoS-атака), код будет написан хорошо. По всему остальному любой, даже неопытный разработчик сможет защититься.
— Убери, пожалуйста, typealias
, он прячет итератор и цикл.
— Спасибо за предложение, но я считаю, что так лучше читается, сразу по коду видно, где не просто итератор, а именно DetailsProcessor
. Plain old domain specific design.
— Помести всю логику в одну функцию или хотя бы в один файл.
— А это у меня SRP
(Solid), «high cohesion low coupling». Я предпочитаю не связывать логику обработки заказов и рекламы. Вдруг потом это будут разные сервисы, не стоит связывать их.
— Зачем используешь то range
, то while
, то итератор
— это создает метальную нагрузку? Давай везде сделаем for
.
— Да ладно, чего там сложного, разработчик должен знать базовые конструкции языка.
— Я вижу 3 вложенных цикла и поход в базу, сделай все без циклов, пожалуйста, они тут не нужны.
— Это преждевременная оптимизация. Давай, если начнутся проблемы, будем думать об оптимизациях. Фичу нужно срочно релизить, сейчас нет времени на рефакторинг.
Маскировка проблем производительности через триггеры
Апологеты техно-анархизма пользуются не только циклами в коде.
С точки зрения коэффициента затрат к выхлопу намного более вкусно выглядит тактика добавления триггеров на таблицы базы данных. Если этим пользоваться не часто, риск компрометации сводится к нулю.
Смекалистый разрушитель корпораций найдет самую большую и часто изменяемую таблицу и повесит триггер на нее. Опытный же мастер своего дела выберет такую таблицу, в которой данных еще немного, но расти они будут быстро.
Этот же прием подошел бы для усиления когнитивной нагрузки, если на триггеры вешать бизнес-логику.
Экспертная маскировка проблем с производительностью
Статей про то, как можно оптимизировать Postgres, очень много, например, вышедшая в совсем недавно «Оптимизация SQL запросов».
Не хочу пересказывать примеры из подобных статей, все что нужно для саботажа — делать обратное рекомендуемому в статьях.
Вместо заключения
Мотивы разрушителей кодовых баз мне не известны. Если это луддизм через обучение нейросетей плохим решениям, то я мог бы этому сочувствовать. О феноменальных возможностях нейросетей писал в своем тг-канале.
Понять можно и анархистов, борющихся с гнётом крупных корпораций.
Если же вы узнали в некоторых примерах себя и не являетесь членом организации, о которой шла речь, прошу не обижаться. Все совершают ошибки, и я не исключение. Хочется порекомендовать юмористическую статью The Grug Brained Developer с примерами «как надо».
Данная статья — попытка посмотреть на код с точки зрения нанесения ущерба кодовой базе — то есть «как не надо». Надеюсь, кому-то поможет писать более простой понятный код.
Кто-то стучится в дверь. Открою, а потом допишу последн...
Комментарии (168)
Sly_tom_cat
18.01.2025 14:26А зачем искать злой умысел там, где все легко объяснить глупостью?
Бизнес давит на разработчиков "давай, давай, быстрее" и вот разработчик находит самый примитивный путь решения и фигачит.
Спросите, а где же тут глупость? Так там, откуда подгоняют.arturdumchev Автор
18.01.2025 14:26Да, согласен.
Другая причина — когда приходишь на новый проект, а тебе дают большую фичу, ты не знаешь деталей проекта, можешь случайно продублировать функциональность или наступаить на какие-то грабли, расставленные предыдущим разработчиком.
Но бывает, когда пишешь плохо сразу — от недостатка опыта, например. В статье есть пример, как
implies
вместе с отрицанием отрицания дает большую сложность. И так пишут — сразу, без всякой спешки, на текущем проекте видел.Иногда переходишь на другой стек и пишешь неидиоматично, просто потому что привык к другому.
То что заговора нет — это понятно, мне интересно было в таком стиле написать статью.
CrazyOpossum
18.01.2025 14:26Приходит пм: "хотим такую фичу." Лид: "спроектировать, написать, протестировать - за месяц справимся". Пм: "мы её уже продали, давайте без тестов, за баги как-нибудь отбрехаемся."
SilverHorse
18.01.2025 14:26Есть вариант еще хуже, когда пм начинает мнить себя программистом, при этом в деталях проекта, который сам же и ведет, не понимает от слова совсем... И метод "х...к, х...к и в продакшен" начинает применяться не потому что дедлайн был вчера, но об этом никому не сказали, а потому что пм художник, он так видит и уже всем наобещал, что будет сделано именно так, и это было одобрено самым высоким руководством, с которым уже не поспоришь...
Dhwtj
18.01.2025 14:26после чего ПМ слышит какой же он дебил,
причем сначала от команды разработки, а через месяц от заказчика (можно, хоть тут не буду писать его с большой буквы?)
хотя, встречал много случаев когда функционал на самом деле не нужен и только для галочки - тогда ПМ просто гений
xSVPx
18.01.2025 14:26Так все тесты при собеседованиях - это удаление гланд через жопу. Каких набирают и обучают, такие и есть.
Т.е. вначале от людей требуют пользоваться совершенно адскими регэкспами какими-нибудь (там где можно и без них) через раз фигачат рекурсию (которая чревата), а потом удивляются чего это в продакшене разобраться что делается можно только прорвавшись через все эти "прикольные штуки".
И уверяю вас, если вы будете писать понятно, интервьюеры вас не поймут. Будут ругать за то, что вы проитерировали через аж десять значений вместо построения индексов и деревьев. А вдруг значений станет миллион (а в задаче значения - это ребенок-подросток-взрослый).
Никто и нигде(почти) не оптимизирует код по метрике "насколько это просто поддерживать", так чего удивляться, что модификации приводят к бедам?
lightmaann
18.01.2025 14:26Далеко ходить не надо. Как-то проходил собеседование в яндекс, и попалась задачка простая на аккумулятор с хитрой логикой. Интервьюер мягко намекнул на использование reduce. Против reduce ничего не имею, но за 10 лет встречал примерно 1.5 человека, которые без бутылки за 2 секунды могли разобраться в том, что оно делает, особенно, когда нет нормального названия у переменной, куда кладётся результат.
Я решил не идти на поводу у собеседующего и написать обычный код. Коллега начал жестко противиться, мол, зачем, если можно написать вот тут однострочник, которые делает 678 операций с массивом. Я ответил, что предпочитаю в продуктовой разработке простой и понятный код, нежели усложнять жизнь коллегам ради красоты и лаконичности. Он смирился, и мы пошли решать дальше.
На следующий день пришел отказ с поинтом, мол "кандидат не идёт на контакт", а дальше ссылка на reduce в MDN. Ну, может оно и к лучшему)arturdumchev Автор
18.01.2025 14:26Очевидно, вас собеседовал адепт организации, о которой писал статью.
VADemon
18.01.2025 14:26кандидат не идёт на контакт
Интересно, а собеседующий пошел на контакт? Очевидно, нет.
WLMike
18.01.2025 14:26Надо сказать честно, большинство разработчиков не умеет писать нормальный код, даже если им давали время. А те кто умеют временщики - среднее время на одном месте по статистике 1,5 года, поэтому не очень напрягаются и пишут, как прийдется
Gradiens
18.01.2025 14:26большинство разработчиков не умеет писать нормальный код, даже если им давали время.
По отдельности в тепличных условиях много кто умеет. Вместе - нет.
Давайте 100 таких умеющих посадим на один проект (неважно, монолит или микросервисы). И будем менять 30 из них каждый год. А требования станем менять еще чаще. Что будет? К гадалке не ходи, через 3 года код станет непроходимым легаси. А через 10 лет им можно будет пугать джунов, да так, что они разочаруются в профессии и побегут в курьеры.
lightmaann
18.01.2025 14:26К сожалению, чистая правда. Из своего опыта:
На проекте было 3 разработчика, мы согласовывали решения между собой, поэтому не было никаких проблем. Потом решили взять ещё двоих. До того, как я пришел на проект, был ещё один разработчик, гадящий в гит, собственно которого я и заменил, и мне было сказано - перед тем, как что-то делать, надо посмотреть, как уже было сделано, и если что, посоветоваться. Затем мне показали тот говнокод, который уже существует (примерно в 3-х местах), и не делать также. Инструкцию я понял, и мы спокойно работали дальше.
Так вот, взяли мы двух новых разработчиков и сказали им ту же идею. Что происходит в течении следующего месяца:
Первый разработчик, не спрашивая никого, берёт и изобретает собственный велосипед, причём который ещё и не работает. На вопрос, почему он не следовал инструкции, которая была дана, он ответил, что "решил проявить себя".
Второй разработчик поступил более элегантно. Однажды меня позвал тимлид посмотреть его код. После просмотра я понял, что этот код уже где-то видел, и меня осенило - он подозрительно похож на те места, которые мне строго запрещалось использовать. Тимлид зовёт его на звонок и спрашивает, "Ваня, почему ты взял здесь это решеие?". А далее, как в анекдоте (ответ убил):
- "Я сделал поиск по проекту и нашел 85 мест, где делается вот так, и 3 места, где делается иначе. Я решил, что эти 3 места - то, что мне нужно. "
Тимлид: "Слушай, а почему ты не догадался спросить у меня или у других ребят, какое конкретно место тебе выбрать..."
- "Я так чувствую".
Выводы, как всегда, делаем сами, но вот такая репрезентация доказывает проблему. Боюсь представить, что происходит, когда на проекте 20 разработчиков.
Sild
18.01.2025 14:26Приготовлюсь собирать летящие тапки, но.
Вот сколько не говорят про говнокод - всегда вспоминают про бизнес, которому "надо быстрее". А бизнесу не то чтобы надо прям дохрена быстрее. Он часто может подождать и час, и 2, и даже пару дней. Да каких там дней, проекты стандартно срываются на месяца - и все живы.
Но не так часто вспоминают что в нашем джентельменском клубе, в общем-то, хватает во-первых - не очень квалифицированных специалистов, назовем это так. У которых если программа скомпилировалась - то она работает. А если она интерпретируемая, то это вообще подарок - "хрен его знает когда там баги всплывут, может я уже уволюсь".
А во-вторых - все люди сильно разные, и бекграунды сильно разные. И код, как ни крути, получается разношертсный. Ну, люди существа такие. Разнородные. Для одного понятнее по-одному, для другого по-другому. Кто-то (я) вообще плюсовик, мне красивый код писать религия не позволяет.
И пример с AspectJ в статье он как-раз об этом - у человека был план, и он его придерживался. Если бы он вел проект от начала и до конца, и этот паттерн был бы в проекте стандартным - вопросов бы ни у кого не было. Проблемы от того, что человек что-то сделал на его взгляд красивое, и ушел.
В общем не стоит все скидывать на бизнес, мы (разработчики) тоже так себе людишки.
FODD
18.01.2025 14:26Плюс опять-же, человеку даже если пальцем тыкать в говнокод, который по его понятиям норм, он не измениться никогда.
Я (фронтендер) в начале пути просто обожал миксины (подвид множественного наследования). Пихал их по поводу и без везде, было удобно и понятно, код переиспользуется, все классно. А потом попал на проект другого такого дурачка и проклял эту фичу
adeshere
18.01.2025 14:26А зачем искать злой умысел там, где все легко объяснить глупостью?
Про глупость и злой умысел верно, но есть чуть более емкая формулировка: как известно, миром правит не тайная ложа, а явная лажа (с) ;-)
Но тем не менее, рискну предположить, что догма про плохой код все-таки не универсальна. Несмотря на мое безусловное доверие к опыту @arturdumchev:
Я уже 10 лет в разработке. Такого, чтобы прийти на проект, которые поддерживается 3+ лет и чтобы кодовая база была хорошей, — не было ни разу. Смотрел на код друзей, работающих в других компаниях. Самое-самое ужасное, что видел — размер функции main в java-приложении на несколько десятков тысяч строк кода.
я бы все-таки задал ему такой вопрос: а что в таком случае будет с проектом, который поддерживается 30+ лет? Код будет хуже в 10-й степени?
Что-то у меня есть сомнения, что это всегда так. Несмотря на мои крайне слабые знания разных ОС, а тем более кода их ядер, из чтения Хабра складывается впечатление, что с тем же Линуксом, несмотря на многодесятилетнюю преемственность ядра, не все так ужасно (злые языки говорят, что бывают и кодовые базы похуже ;-). Ну или рискну привести
пример из личного опыта (крайне скромного, но все-таки).
Проекту 35 лет. Функция main (cpp, это главный корень проекта) около 200 строк. Фортрановским MAIN (с жуткими процедурными циклами обработки сообщений) 400-600 строк. Причем половина из этих строк - это комментарии, которые визуально фрагментируют "простынку" на логически монолитные секции с более-менее понятной структурой (обычно не больше экрана кода, иногда много меньше). Библиотеки оформлены в этом же стиле. Другие функции, кроме MAIN, обычно
еще кратно короче
Исключение - это функции с сугубо линейной логикой, где блоки кода выполняются строго последовательно. А единственное исключение - это "аварийный" выход из какого-то блока. Там тоже бывают сотни строк кода, но в этом случае он обязательно разбит на короткие разделы - примерно как текст в большой книге с пронумерованными заголовками 1-2 уровней.
Из супермонстров в проекте присутствует только пара заголовочных файлов: один на две тысячи строк, другой на три. Но, там есть один трюк, который позволяет ориентироваться в этом море текста, даже не зная имен тех объектов, которые надо найти. Если вдруг у кого-то есть аналогичная проблема с монструозными файлами - советую заглянуть под второй спойлер ниже ;-). Да, решение не универсальное, но вдруг кому-то и пригодится? ;-)
Типичный фрагмент главного цикла обработки сообщений
Кусочек главного цикла обработки сообщений в одной из фортран-программ с наиболее заумной внутренней логикой
(...) c c===========================================================================c c Выполнить выбранную операцию: c===========================================================================c c 80 SELECT CASE(ABD_Mode) c c.........101. Создать новый ряд или поправить паспорт имеющегося: CASE(-$F2) call abdnew('new'); call restore_help() if (dbh.nf >= 1) cycle ABD_Mode=-$Esc; goto 80 c CASE(-$CtrlF2, -$R); call abdnew('old') if (ied == 1) then; ABD_Mode=-$F4; goto 80; end if c c.........102. Разные виды импорта данных: CASE(-$F3); call abdimport() c CASE(-$AltF3); call edit_ximport_table() c CASE(-$CtrlF3); call abdximport() c c Если установлен автоматический режим, то сразу после XIMPORT - выход: if (Plot_Mode == $PM_ABDxImport) exit c CASE(-$CtrlF4) dos_line=' Объединить две базы данных' ansyer_default='y'; call abdmain_ask('AbdDBUnion') if (ansyer == 'Y') call abdDBUnion() goto 60 c c.........103. Редактирование данных: CASE(-$F4) CB_Mode=$CB_Mode_Edit; ied=abdedit(); CB_Mode=$CB_Mode_String if (ied == 1) then; ABD_Mode=-$CtrlF2; goto 80; end if ied=0 c c.........104. Составить отчет о наличии данных: CASE(-$F5,-$AltF5) i=1; if(ABD_Mode == -$F5) i=0 ! Краткий/Подробный call set_help_topic('PrintReport'); call abdreport(i)
А вот фрагменты одного из двух имеющихся в проекте монструозных заголовочных файлов
Тех самых, где 2 и 3 тысячи строк одной простыней. А трюк в том, что каждая строка в шапке-оглавлении монстра точно соответствует аналогичной строке в теле файла. Если забыл название структуры, то ищешь нужную строку в "оглавлении", затем Ctrl+C, Ctrl+F - и ты в соответствующем подразделе (фрагменте) монстра, где на 1-2 экранах собраны все объявления по выбранной теме.
Это фрагмент из начала файла:
c WinABD_inc.for, версия 4.10.42 c План файла: c c 0. Глобальные параметры процесса c c c=* 1. БАЗОВЫЕ ТИПЫ И ГЛОБАЛЬНЫЕ ДАННЫЕ *=c c 1.1. Строки для разных нужд (имена файлов, топики справки и др.) c c 1.2. Мнемонические константы и базовые структуры для разных нужд c c 1.3. Текстовый слой. Структуры для описания цвета, окна, символа и др. c 1.4. Текстовый экран: взаимодействие с сервером c c c c=* 2. ТИПЫ И ГЛОБАЛЬНЫЕ ДАННЫЕ ДЛЯ ГРАФИКИ *=c c c 2.0. МАКСИМАЛЬНЫЙ РАЗЕР ПАКЕТА РЯДОВ $MAXSERIES c c 2.1. Графика: работа с таблицей команд c c 2.1.3. Константы для настройки шрифта: c 2.1.4. Константы для настройки типа и толщины линии: c 2.2. Предопределенные номера сегментов и текстов для разных нужд c c 2.3. Глобальные параметры виртуального графического экрана c c 2.5. Работа с RBOX c c c=* 3. РАБОТА С ЦВЕТОМ *=c c 3.1. Стандартные цвета = именованные константы c c 3.1.1. Стандартные цвета графиков (частично повторяют текст-цвета)c c 3.1.2. Стандартные цвета = именованные константы c c 3.2. Работа с прозрачностью в разных функциях (замена XOR-моды) c c 3.3. Составной цвет для текстового экрана: символ+фон c c 3.4. Стандартные цвета для графики c c 3.5. Градиентные цвета для графики c c 3.6. Управление печатью и экспортом рисунков, таблица подмены цветов c c c=* 4. БАЗОВЫЕ ТИПЫ И МНЕМОКОНСТАНТЫ ДЛЯ РАБОТЫ С КЛАВИАТУРОЙ. *=c c 4.1. SCAN-коды клавиатуры: c c 4.2. Специальные "внутренние" скан-коды ABD для обработки особых случаев c 4.3. Псевдо SCAN-коды для обработки событий мыши c 4.4. Флаги вставка/замена, наличия символа, макроса. Последний SCAN-код. c c c=* 5. МЫШКА, ВСПЛЫВАЮЩИЕ ПОДСКАЗКИ, КОНТЕКСТНОЕ МЕНЮ, "МЕНЮ F1-F12" c 5.1. Константы и типы для обработки событий мышки: c c Регионы графического/текстового экрана, режим адресации c 5.2. Работа с всплывающими подсказками: c c c c=* 6. РАБОТА С КОНТЕКСТНЫМ МЕНЮ *=c c 6.1. Максимальная длина элемента КМ, размера КМ и число регионов КМ c 6.2. Идентификаторы КМ для использования в различных режимах. c 6.3. Элемент контекстного меню: c 6.4. Контекстное меню: массив элементов c 6.5. Активная таблица КМ = PPMENU_Regions($PPMENU_MAX_REGIONS) c 6.6. Альтернативы для создания КМ: c c c=* 7. ТЕКСТОВЫЕ ЭКРАНЫ, РАЗНОЕ *=c c 7.1. Стек текстовых окон, УТМ, ВП и КМ (текстовый режим): c c 7.2. Структуры данных для "меню функциональных клавиш" c c 7.3. Структуры данных для "меню кнопок": c c
А это - фрагмент из "где-то далеко, очень далеко..." в его середине:
c c c=======================================================================c c=*********************************************************************=c c=*===================================================================*=c c=* *=c c=* 5. МЫШКА, ВСПЛЫВАЮЩИЕ ПОДСКАЗКИ, КОНТЕКСТНОЕ МЕНЮ, "МЕНЮ F1-F12" c=* *=c c=*===================================================================*=c c=*********************************************************************=c c=======================================================================c c c c c=======================================================================c c 5.1. Константы и типы для обработки событий мышки: c c=======================================================================c c c.....5.1.1. События мыши: integer*4, parameter :: + $MOUSE_LCLICK=1, $MOUSE_RCLICK=2, $MOUSE_CLICK=3, + $MOUSE_LDBLCLICK=4,$MOUSE_RDBLCLICK=8, $MOUSE_DBLCLICK=12, + $MOUSE_HOVER=1024, $MOUSE_MOVE=2048, $MOUSE_ALL=2048*2-1 logical*4 :: Mouse_Jump=$True ! Разрешается ли "перескок" мышки в окно ввода c c.....5.1.2. Вид курсора мыши (устанавливается SetMouseCursor).
Я конечно не знаю: может по современным меркам это как раз и есть то самое
жуткое легаси
формально оно так и есть: написано лет 30 назад, серьезно рефакторилось последний раз лет 20 назад
в котором я могу разобраться лишь по причине участия в этом проекте с его начала. Поэтому было бы интересно узнать впечатления при "взгляде со стороны". Но отдельные случаи "вхождения в проект" с нуля (причем оба разработчика фортран раньше не знали, а писали на С++) у нас были, и проблем вроде бы не возникло
А вопрос к автору статьи в том, что к его тезису про неизбежность деградации кодовой базы любого развивающегося проекта наверно нужны какие-то уточнения? Например, кроме требования о возрасте (3+ лет), надо добавить требование о текучести кадров? Или еще что-нибудь?
Или все-таки в общем случае достаточно одного условия 3+ лет? А те аномальные ситуации, когда более древняя кодовая база не деградирует с приемлемой скоростью, - это досадные исключения, лишь подтверждающие общее правило?
;-)
xxxDef
18.01.2025 14:26У меня проект который я начинал с нуля и командую им уже 20 лет. Люди приходили-уходили, а я был на нем всегда и в силу этого там очень много именно моего кода. И этот код - полная деградация. Хотя сам по себе я в каждый момент времени - молодец. Вот только навыки, опыт и знания растут, подходы меняются, а уже написанный код остается. И начинает выглядеть как коричневая субстанция. И я более чем уверен что мой сегодняшний код через 5 лет будет выглядеть так же. И никак не спихнешь это на нерадивых джунов или торопящихся менеджеров, хотя и такого много.
Просто есть одно правило - если вам нравится код, написанный вами несколько лет назад - значит все эти годы вы не развивались.
adeshere
18.01.2025 14:26Просто есть одно правило - если вам нравится код, написанный вами несколько лет назад - значит все эти годы вы не развивались.
Круто Вы сейчас меня опустили! ;-) Люто плюсую за такой троллинг ;-)) А если чуть-чуть серьезнее, то собственный старый код наверно не нравится никому. Тем более, что развивается не только сам автор кода, но и инструменты еще иногда. Да даже внешний контекст - он меняется, и требует адекватного отражения в коде.
Вопрос скорее не в том, нравится тебе старый код или нет, а в том, насколько он понятен спустя много лет, и
насколько легко поддается рефакторингу
Меня самого на начальном этапе учил мой старший коллега, инициатор проекта и основной мой соавтор на первом этапе его развития. Я был зелен и относился с известным пиететом и к нему самому, и к его коду, естественно. Так как до этого меня в ВУЗе учили кодить на перфокартах, экономя не только операторы, но и символы ;-)
А спустя много лет я вдруг обнаружил, что рефакторить мой код, даже совершенно забытые фрагменты, мне гораздо легче, чем моему соавтору сделать то же самое со своим кодом аналогичного возраста. Так и хочется вспомнить известную фразу
"Делай, как я говорю! Не делай так, как я делаю"
которая у меня почему-то ассоциируется с Козьмой Прутковым, но прямого подтверждения его авторства я сейчас не нашел
Можно ли сказать, что разработчик перестал расти, если у него нет проблем с чтением и с поддержкой собственного кода 15-летней давности?
AlexXYZ
18.01.2025 14:26Тем более, что развивается не только сам автор кода, но и инструменты еще иногда.
Ну так вот так может можно начать считать себя хорошим программистом, когда уже начинаешь писать инструменты для своей работы? И вроде как и без них можно сделать, но ведь с ними быстрее
Скрытый текст
(я как-то написал перевод несложных экселевских таблиц в C#, потом было прикольно, когда сам заказчик писал формулы, добивался, чтобы они корректно работали, а я уже почти без дополнительных усилий вставлял его код в свой проект. Ещё и ЗП за это получал).
adeshere
18.01.2025 14:26Ну так вот так может можно начать считать себя хорошим программистом, когда уже начинаешь писать инструменты для своей работы?
Я дико извиняюсь
(я сам в очень специфической сфере работаю)
программирование научных задач
Только не смейтесь, пожалуйста, если глупость спрошу. Но разве в природе есть программисты с многолетним опытом, которые
НЕ пишут собственных инструментов?
Особые случаи "летунов" не в счет (хотя и у них наверняка есть собственные pet-проекты). Но если ты много лет кодишь какие-то похожие вещи, то ведь это наверно превратится в нудятину? Которую неизбежно захочется автоматизировать каким-либо способом, чтобы в освободившееся время заняться чем-то более творческим?
Или я слишком отстал от жизни в своем диком заповеднике, и работа в больших командах с жесткими правилами далеко не всегда оставляет такую возможность?
arturdumchev Автор
18.01.2025 14:26что с тем же Линуксом, несмотря на многодесятилетнюю преемственность ядра, не все так ужасно
В популярном open source всё в среднем получше. Не берусь перечислить все причины, это объясняющие, но вот несколько:
Когда у тебя open source проект, ты можешь просить сделать так, как видишь ты. У автора ПР не будет выбора. Менеджеры, сроки, завязки на маркетинг тут не помогут влить ПР.
Когда профессионалу не платят (при благотворительности), он выкладывается на максимум. Это моя вольная интерпретация идеи из книги Дэна Ариели “Предсказуемая иррациональность“.
Когда что-то пишешь в open source, не мешают мысли вроде: “надо быстрее“ или “мне все равно тут работать еще полгода — и уволюсь“.
Пишешь в open source — увидит весь мир.
Средний уровень разработчика, поддеривающего популярный open source проект, выше среднего уровня разработчика средней компании. Не могу доказать, но ощущается так.
А вопрос к автору статьи в том, что к его тезису про неизбежность деградации кодовой базы любого развивающегося проекта наверно нужны какие-то уточнения?
Попробую и тут перечислить часть причин, почему кодовая база протухает за несколько лет:
Текучка. Новые разработчики и проект не знают, и хотят затащить новые библиотеки и подходы (иногда), создавая зоопарк.
Постоянно горящие сроки — ошибки проектирования в спешке.
Меняющиеся требования, когда фича уже реализована и требует рефакторинга на скорую руку.
Количество джунов на проекте.
Отсутствие процесса: нет code review на начальном этапе (или вообще всегда), нет встреч по защите архитектуры и т.п.
Даже язык программирования, на котором пишут, влияет. Пример — Android-приложение на Scala. Бизнесу надо будет нанимать новых сотрудников, у которых опыта под андроид на нестандартном языке гарантированно не будет. Кроме того, всплывет море проблем связанных с нестандартным ЯП. В случае со Scala время комплияцие значительно возрастет.
Или все-таки в общем случае достаточно одного условия 3+ лет?
Взял цифру 3, пробежавшись мысленно по собственному опыту, вспоминая плохие кодовые базы. Во всех возраст был 3+ лет. Уверен, можно написать отвратительно за первый месяц — сам так делал на первой работе :)
tolyanski
18.01.2025 14:26Взял цифру 3, пробежавшись мысленно по собственному опыту, вспоминая плохие кодовые базы. Во всех возраст был 3+ лет.
Мне кстати вспомнился один проект, который на момент моего прихода был прямо свежий, написан 3-4 месяца назад и буквально передо мной зарелизен. Тем не менее, код выглядел как дичайшее легаси из 2000-х :)
dmiche
18.01.2025 14:26Это был сарказм. Мне понра :)
А горькая правда в том, что это лишь на 20% объясняется глупостью и спешкой. Многое из здесь приведённого - это тот способ кодирования, который делается степенно, со вкусом и уважением к собственной квадратноголовости. И вот есть большая когорта людей, которые оттачивают этот навык.
tolyanski
18.01.2025 14:26Поддерживаю. Более того, если мозги и опыт на месте, то волей неволей начинаешь писать адекватный поддерживаемый код даже в условиях дикой спешки, просто потому что привык делать нормально. И за этим следует еще одно открытие: даже в дикой спешке можно писать чистый код и при этом успевать по срокам
sshikov
18.01.2025 14:26Статей про то, как можно оптимизировать Postgres, очень много, например, вышедшая в совсем недавно «Оптимизация SQL запросов».
Не хочу пересказывать примеры из подобных статей, все что нужно для саботажа — делать обратное рекомендуемому в статьях.
Только не в этом случае. Я бы сказал что автор как раз из "этих", из вашей тайной организации.
SergeiZababurin
18.01.2025 14:26То что описанно в статье, это следствие огромного колличества школ. Там учат писать по книгам, но ученики не понимают для чего что то делается и в каких случаях можно что то дедать, а что то нельзя.
Результат.
Человек пишет код, который читать невозможно, при этом все аргументы в пользу кода на высшем уровне. Железная аргументация позволяет снискать авторитет и продвижение по лестнице. А написанный говнокод скидывается на других.
У таких людей нет заинтересованности поддерживать код и развивать, но есть заинтересованность красиво его представить.
Вот например статья, в которой кнопку делают через обсервер. (из пушки по воробьям )
https://habr.com/ru/articles/874302/vDyDHp8
18.01.2025 14:26Про школы только если на проекте нет ни мидла, ни сеньора чтобы в мердж реквесте замечание сделать. Имхо , как уже говорили выше, часто говнокод это трейдофф со временем. Или поленился кто-то, например , тесты написать на свою поделку.
Про Яндекс вообще странно такое слышать.
SergeiZababurin
18.01.2025 14:26На ревью такой код лучше проходит чем читаемый как правило, Это тоже в статье есть. Здесь не зависит от ревювера этот момент.
— Убери, пожалуйста,
typealias
, он прячет итератор и цикл.
— Спасибо за предложение, но я считаю, что так лучше читается, сразу по коду видно, где не просто итератор, а именноDetailsProcessor
. Plain old domain specific design.— Помести всю логику в одну функцию или хотя бы в один файл.
— А это у меняSRP
(Solid), «high cohesion low coupling». Я предпочитаю не связывать логику обработки заказов и рекламы. Вдруг потом это будут разные сервисы, не стоит связывать их.— Зачем используешь то
range
, тоwhile
, тоитератор
— это создает метальную нагрузку? Давай везде сделаемfor
.
— Да ладно, чего там сложного, разработчик должен знать базовые конструкции языка.— Я вижу 3 вложенных цикла и поход в базу, сделай все без циклов, пожалуйста, они тут не нужны.
— Это преждевременная оптимизация. Давай, если начнутся проблемы, будем думать об оптимизациях. Фичу нужно срочно релизить, сейчас нет времени на рефакторинг.трейдофф со временем.
Далеко не всегда на это похоже, потому что простые решения зачастую пишутся быстрее. Запутанные варианты приходится делать дольше.
Про все языки не знаю. Знаю что это точно про js. Особенно после появления культа реакта (который сам по себе является адом колбеков (http://callbackhell.ru/) возникает вопрос.
Кому и зачем это надо ?
arturdumchev Автор
18.01.2025 14:26Еще бывает так, что джун/милд уже написал огромную фичу, там много косяков, страшная архитектура, но фича нужна позарез, потому что уже и маркетинг завязан, и менеджеры требуют скорее — и приходится это вливать.
А почему про Яндекс странно? Везде накапливается легаси, везде бывают сложные времена с текучкой, когда приходит новая команда и пишет фичи, не понимая старый код, затаскивая кучу новых технологий, дублируя функциональность и т.п.
Wicirelllis
18.01.2025 14:26С другой стороны кто дал джуну в одиночку писать огромную фичу?
arturdumchev Автор
18.01.2025 14:26Я с таким и в Яндексе, и в Сбердевайсах, и в Австралийском стартапе AudienceRepublic сталкивался. И сам, будучи джуном, писал приложения с нуля в аутсорс компании. Не завидую тем, кто был вынужден это поддеривать.
arturdumchev Автор
18.01.2025 14:26Я думаю, тут очень много причин, почему код усложняют или пишут “грязно“.
Иногда это следствите того, что люди не своим делом занимаются — т.е. не любят программировать и не хотят разбираться, как писать простой код. Кто-то может усложнять из-за комплексов, желания исползовать ”умные” решения.
Иногда просто надо очень быстро написать, а потом меняются требования, и надо быстро зарефакторить — и вылезают косяки.
x012
18.01.2025 14:26Члены этой тайного сообщества уже проникли в ркн или еще нет?
arturdumchev Автор
18.01.2025 14:26Судя по всему, да, недавно попадались новости, что они случайно Youtube разблокировали
x012
18.01.2025 14:26Интересненько...
И как далеко сия организация разовьется?Кто следующий объект проникновения?
Тайные общества мировых правительств?arturdumchev Автор
18.01.2025 14:26К сожалению, я пока не располагаю данными. Будет больше данных — буду делиться.
Cerberuser
18.01.2025 14:26AspectJ, на самом деле, можно сделать более-менее удобоваримым - по крайней мере, в одном из доставшихся мне проектов (в целом весьма, кхм, отсаботированном) это, на мой субъективный взгляд, удалось. Конкретно, там аспекты использовались по принципу "если на методе есть такая аннотация - завернуть его в такую обёртку". В итоге получалось, что, с одной стороны, ничего неявного в этом коде нет (аннотация на виду, что она означает - задокументировано), с другой - какие-то общие вещи, вроде логирования, таким образом делать оказалось вполне удобно. Хотя, конечно, мне потребовалось время, чтобы во всё это въехать, но на общем фоне это смотрелось скорее положительно.
worldaround
18.01.2025 14:26Господа, я как раз из этой организации. Специально не писал ничего на хабре со времен царя гороха, но раз уж нашу сетку вскрыли, решил, наконец, высказаться.
И где-то в другом файле пишется:
val (_, id) = getUser()
Если ваш язык позволяет это скомпилировать, то ищите истинного злодея не в яндексе.
Спойлер
Напоследок, если хотите избавить свою жизнь от взаимодействия с противоречивыми параграфами в программировании, просто используйте языки с жесткой типизацией и, вообще, такие языки, у разработчиков которых было нормально с головой.
arturdumchev Автор
18.01.2025 14:26Kotlin как раз strongly staticaly typed.
Код превратится в:
val (_, id: Int) = getUser()
Тут проблема в том, что и age, и id — одного типа
Int
, а destructuring возвращает компоненты по порядку из указания в декларацииdata class
.Qwertovsky
18.01.2025 14:26В нормальном языке порядок неважен. Destructuring с объектами работает по именам. Так в JavaScript или Rust. Проблема в языке.
worldaround
18.01.2025 14:26strongly staticaly typed и тысяча способов обойти это недоразумение, класс! :)
Тяжесть закона компенсируется неведением, что творишь...
Arioch
18.01.2025 14:26да сфига ли он strongly typed, если у него age и id - один тип данных?
age - это duration и измеряется в секундах или любых других единицах времени, а совсем не безразмерный int
народ жалуется, выкручивается как может, но насколько понимаю в язык - и в ту самую идиоматичность - этого так и не завезли. https://discuss.kotlinlang.org/t/units-of-measure/3454
arturdumchev Автор
18.01.2025 14:26strongly typed — это описание типизации языка; язык не позволяет смешивать разные типы и не выполняет автоматические неявные преобразования. Например, нельзя умножить строку на число. Kotlin — strongly typed. Проблем с destructuring решится, если как раз в id и age разные типы засунуть, как я в статье в качестве решения предлагал.
Spinoza0
18.01.2025 14:26Так это одна из фишек языка, только нужно с умом использовать ) Автор даже подсказал, что value class спасёт
shashurup
18.01.2025 14:26По-моему, в F# была подходящая фича которую не спешат тянуть в другие языки - единицы измерения в качестве модификатора типа. Фича позволяла без особой когнитивной нагрузки избежать ошибок когда метры могут быть в качестве килограммов использованы.
Alexandr140
18.01.2025 14:26Работал в Сбедивайсах с беглыми из Яндекса. Подтверждаю
Шизофреническое усложнение архитектуры проекта, в ревью - постоянные переименования переменных. Документации не было вообще никакой. При этом у всех этих светил была полная профнепригодность в практических задачах. Когда задавал вопросы - звонили и закатывали истерики. Страдали от них в основном исполнители, которых набирали со стороны и которые не задерживались. Было это до февраля 2022 года.
arturdumchev Автор
18.01.2025 14:26Здравствуйте, коллега, на каком проекте вы были? Я с 2020 под 2022 написал и поддерживал медиа-приложения на девайсах.
ainoneko
18.01.2025 14:26А кто-нибудь может рассказать, как разрабатывают транспортную карту "СберТройка"?
(И тестируют ли её не только на живых людях?)В Новосибирске (не знаю, как в других городах, где её тоже вводят) её (и терминалы/валидаторы, через которые можно платить банковскими картами) ввели вместо уже работавшей и отлаженной ЦФТшной ЕТК --- и сразу несколько критичных (но только для пользователей) багов:
после прикладывания к валидатору может пройти до 10 секунд (а к "коробочке у кондуктора" -- до целой остановки), а у 5% социальных карт не работает совсем (но это не проблема: пассажиры оплатили банковскими картами или наличными, некоторые могут получит возврат денег, надо всего лишь отправить имейл и "В письме нужно указать номер социальной карты и прикрепить платежный документ об оплате проезда банковской картой из приложения или личного кабинета пассажира.");
когда был сбой у систем связи, за многие поездки оплату взяли несколько раз (у кого-то --- семь раз за две поездки) (но это тоже не проблема) -- то есть всё работает только в идеальных условиях.
Как обычно, предлагается потерпеть: «Сейчас период сложный, и со временем всё стабилизируется».
И "Сейчас ведется работа над тем, чтобы добиться скорости срабатывания до пяти секунд (пока она больше)."
vadimr
18.01.2025 14:26Настоящие программисты не будут писать функцию для импликации, они используют операцию
<=
Но вообще-то в конкретном примере из статьи проще написать
shouldGoHome = (isRaining && !carryingUmbrella)
, а в общем претензия к импликации неясна.В целом, вкорячивать условные операторы для вычисления булевского значения по таблице истинности – неважная практика. Так как во-первых, можно забыть одну из веток условий, а во-вторых, это может неэффективно транслироваться.
А макросы – это вообще святое.
arturdumchev Автор
18.01.2025 14:26shouldGoHome = (isRaining && !carryingUmbrella)
В черновой версии статьи был такой код. Я решил изменить на
if
, чтобы читалось чуть проще, но это субъективно, конечно. Были бы примеры кода наLua
, так бы и оставил.Тут тезис про то, что если то же самое через импликацию писать, читается сложнее. А претензии к импликации те же, что и у разработчиков большинства языков, которые не включают
implies
как ключевое слово в кор языка. Тема обсуждалась много раз.А макросы – это вообще святое.
Ловите, один уже скомпрометировал себя!
vadimr
18.01.2025 14:26Так именно потому, что импликация – это просто-напросто <= на булевском типе, для неё и нет отдельного ключевого слова.
arturdumchev Автор
18.01.2025 14:26Но мы же не можем в java, kotlin, go, clojure, dart, lua, fennel применить `<= ` к двум boolean. И ключевого слова
implies
нет.vadimr
18.01.2025 14:26Ну clojure вообще не из этой оперы, там есть условная функция, которая позволяет всё это сформулировать изящнее, да и можно написать макрос. Хотя в racket есть implies из коробки.
А вот почему в языках типа java нельзя применять операции сравнения к примитивному булевскому типу, хотя это самое натуральное перечисление из двух значений, лично мне непонятно, и это выходит за рамки вопроса об импликации. А как вы в java будете сортировать по булевскому полю? Инкапсулируя его в объект и применяя compareTo? Хорошо ли это?
arturdumchev Автор
18.01.2025 14:26А как вы в java будете сортировать по булевскому полю? Инкапсулируя его в объект и применяя compareTo? Хорошо ли это?
В зависимости от задачи.
Можно boolean-ы представить единицами и нулями и использовать Arrays.sort. Можно самоу алгоритм реализовать за один проход, если создаем другой массив.
Если надо сортировать объекты по boolean полю, можно объектам реализовать Comparable или передать реализацию Comparator<Obj> в место, где это требутся (например, TreeMap).
Arioch
18.01.2025 14:26сортировать по булевскому полю
а зачем? селективность такого индекса будет очень плохой, лучше его избегать и изменить подход к задаче. Ну а если не получается - на некоторых, редких задачах - то придется страдать. То, что язык отталкивает от самой идеи "сортировать по boolean" - это как раз хорошо.
Промышленные языки вообще ценны не свободами (раздвигать горизонты - это удел академических языков), а оковами (заставить всю команду писать одинаково, в том числе дебила Васю и студента Петю). То, что Java мягко отталкивает Петю от плохой идеи, вынуждая его морщиться и лапками писать нудные вещи - это как раз хорошо. Может быть он вспомнит о благодетельности лени и начнет меньше писать, но больше думать.
vadimr
18.01.2025 14:26Точно. Потом будет Вова править программу и радоваться узнаванию: "а, разобрался, этот код нужен, потому что это оковы, на которых Петя укрощал свою гордыню!"
arturdumchev Автор
18.01.2025 14:26А это вы это про себя пишете? Просто вы начали с
Настоящие программисты не будут писать функцию для импликации, они используют операцию
<=
randomsimplenumber
18.01.2025 14:26применить `<= ` к двум boolean
А что больше, true или false?
vadimr
18.01.2025 14:26А как определён булевский тип?
Но вообще-то всегда true > false.
randomsimplenumber
18.01.2025 14:26А как определён булевский тип?
Если #define false 0 то так и есть. А так, если определена операция > , то должна быть определена и операция -. Чему равно false - true?
vadimr
18.01.2025 14:26Если #define false 0 то так и есть.
Это не определение типа.
А так, если определена операция > , то должна быть определена и операция -
С чего вдруг?
Операция >= (и это, кстати, не совсем то же самое, что >) говорит только о том, что множество значений упорядочено.
Если Вася умнее Пети, то чему равна разность их умов?
randomsimplenumber
18.01.2025 14:26Если Вася умнее Пети, то чему равна разность их умов?
У одного из них больше баллов IQ, да?
множество значений упорядочено.
Множество {true, false} действительно упорядоченно? Или просто так получилось, что раз один элемент кодируется как 0, а другой как 1, то один автоматически больше другого?
vadimr
18.01.2025 14:26У одного из них больше баллов IQ, да?
Нет, конечно. Я утверждаю, что Дональд Кнут умнее Аллы Пугачёвой, хотя их баллов IQ не знаю, и подозреваю, что они могли и не измерять их.
И в общем я не думаю, что баллы IQ являются мерой ума, хотя некоторая корреляция есть.
Множество {true, false} действительно упорядоченно?
Множество значений [false, true] очевидным образом упорядочено. Просто потому, что оно так записано.
Или просто так получилось, что раз один элемент кодируется как 0, а другой как 1, то один автоматически больше другого?
Нет, конечно. Истину можно и минус единицей закодировать в какой-то интерпретации. Но это не меняет семантики типа.
randomsimplenumber
18.01.2025 14:26Множество значений [false, true] очевидным образом упорядочено. Просто потому, что оно так записано.
Тогда неупорядоченных множеств не существует, правильно?
утверждаю, что Дональд Кнут умнее Аллы Пугачёвой
Какие ваши доказательства? ;) В математике без этого никак.
Истину можно и минус единицей закодировать
А можно и листочком. А ложь - камешком. Что больше - листочек или камешек?
vadimr
18.01.2025 14:26Тогда неупорядоченных множеств не существует, правильно?
Существуют, но не в компьютере.
Хотя в языках программирования из соображений эффективности обычно не реализовывают отношения упорядоченности на агрегатных типах данных (кроме строк символов, если их считать таковыми). Не потому что в принципе нельзя, а потому что сложно в переносимой реализации и крайне мало востребовано (кроме строк символов).
Кстати, вот, на строках определено сравнение, но не определено вычитание. И вы задолбаетесь придумывать вычитание строк в UTF-8. Хотя Кнут может и смог бы :)
утверждаю, что Дональд Кнут умнее Аллы Пугачёвой
Какие ваши доказательства? ;) В математике без этого никак.
Это просто моё утверждение, которое может быть истинным или ложным.
Но для его доказательства не обязательно численно измерять их ум.
А можно и листочком. А ложь - камешком. Что больше - листочек или камешек?
Смотря как определён тип. [листочек, камешек] или [камешек, листочек].
По общепринятой конвенции true > false точно в том же смысле, как 'R' > 'Q'. В отношении листочков и камешков применимая конвенция мне неизвестна, поэтому тут есть свобода реализации. В конечном итоге вам всё равно придётся как-то упорядочивать листочки и камешки, если займётесь сортировкой предметов.
randomsimplenumber
18.01.2025 14:26Кстати, вот, на строках определено сравнение
Очень плохо определено. Unicode передает привет, в зависимости от локали символы сравниваются немного по разному.
По общепринятой конвенции true > false
Потому что #define false 0, так повелось, что false кодируют логическим нулем.. #define false -1 - и всё сломается. Во всех таблицах истинности закодировано 0/1 а не true/false
вам всё равно придётся как-то упорядочивать листочки и камешки, если займётесь сортировкой предметов.
Ну похоже создатели языка, в котором нельзя просто сортировать по Boolean, считают что это множество неупорядоченное. Непривычно, да. Но выглядит логично.
vadimr
18.01.2025 14:26Очень плохо определено. Unicode передает привет, в зависимости от локали символы сравниваются немного по разному.
Конечно, они сравниваются по-разному в разных локалях. Никто ничего иного и не обещал. Так устроена жизнь, а не язык программирования.
#define false -1 - и всё сломается
Смотря знаковым или беззнаковым сравнением пользоваться.
Но вообще-то это не имеет отношения к обсуждаемому вопросу, потому что само по себе определение констант true и false (как в старом Си) не означает наличия булевского типа. И вы тут подменяете понятия, сравнивая целые числа по имени true и false, а не логические значения.
В языке Форт, кстати, ложь и кодируется минус единицей (точнее, целым числом, состоящим из единичных битов – это равно -1 в случае использования дополнительного кода). Но это никак не противоречит моим словам, потому что в Форте есть только один тип – целые числа, и булевских значений просто не существует, а оператор IF по сути является арифметическим, а не логическим (как, кстати, и в Си).
arturdumchev Автор
18.01.2025 14:26На самом деле, по поводу макросов — все зависит от области применения.
Мне как разработчику, который приходит на новый проект, не хочется разбираться, что понапридумывали другие и какие велосипеды изобрели. Практичеаски все задачи можно решить самым базовым синтаксисом, а оттого, что кто-то на макросах напишет свой ЯП внутри другого ЯП (пример — redplanetlabs так сделали), никому пользы не будет. Но понимаю, что писать такое — большое удовольствие.
Если речь о написании фреймворка, макросы имеют право на жизнь и бывают очень красивы. Пример —
nest
в ClojureDart, писал об этом в статье Зачем Clojure Flutter.shashurup
18.01.2025 14:26Еще в Common Lisp'овых гайдах пытались предупреждать о том, что макросами не стоит злоупотреблять - там где можно функцией обойтись НУЖНО ею обходиться. И вообще, - макрос только если всем участникам очевидно что получилось удачно.
При этом, к сожалению, какого-то более простого критерия уместности макроса никто так и не придумал :(vadimr
18.01.2025 14:26Ну надо сказать, что в старом лиспе просто система макросов была довольно запутана. Современные гигиеничные макросы (такие как define-syntax в scheme) по своему определению и применению мало отличаются от функций, и подобное требование может диктоваться только эффективностью и удобством применения (например, макрос нельзя передать в функцию высшего порядка). Любой вменяемый человек и так при прочих равных условиях напишет функцию вместо макроса, так как функция универсальнее по своему применению. Но по сути дела, гигиеничный макрос в лиспе - это просто альтернативный способ передачи параметров, то есть реализация специальной формы. Я что-то не видел, например, критиков передачи параметров по ссылке в C++.
Кроме того, надо отметить, что в последние десятилетия все расширения Лиспа реализуются через макросы, что гораздо веселее, чем переписывать транслятор.
А что касается
разбираться, что понапридумывали другие и какие велосипеды изобрели.
- то это относится к любому коду, а не только к макросам. В иерархии классов разве не надо разбираться? В чём отличие?
shashurup
18.01.2025 14:26Я таки подозреваю, что ограничения которые предлагались были связаны именно со сложностью восприятия их кода чем с локальной проблемой типа гигиены. Мало того что там код создает другой код, дык еще и все эти квотинги\анквотинги жизнь проще не делают. Как мне кажется, именно поэтому в scheme попытались как-то исправить эту ситуацию - define-syntax легче воспринимать.
arturdumchev Автор
18.01.2025 14:26А что касается
“разбираться, что понапридумывали другие и какие велосипеды изобрели.“
- то это относится к любому коду, а не только к макросам. В иерархии классов разве не надо разбираться? В чём отличие?
Это не сопоставимые вещи. Иерархия классов написана на языке, синтаксис которого я знаю. Я могу сразу начать разбираться с бизнес-логикой приложения.
С макросами два шага — сперва нужно разобраться, какой синтаксис придумал себе предыдущий разработчик. Затем уже разбираться с бизнес-логикой.
Тут еще, наверное, можно предположить, что в отличие от синтаксиса мейнстримового языка, который вылизывается командой из десятков или сотен человек — макросы на проекте писал один разработчик, у которого не было ни времени, ни ресурсов сделать это хорошо. И какой-либо ценности в том, чтобы разбираться в его поделках, нет.
Предыдущий абзац — мой опыт работы в Clojure-стартапе, где макросами описывалась схема базы данных и REST api одновременно. Походы в базу тоже были через макросы. Я точно уверен, что если бы у тех разработчиков забрали возможность писать макросы — всем было бы лучше, включая их самих.
vadimr
18.01.2025 14:26По моему опыту, вопросы синтаксиса играют пренебрежимо малую роль в понимании сложной программы.
arturdumchev Автор
18.01.2025 14:26Разве? Вы же не будете писать сразу на байткоде или на ассемблере?
vadimr
18.01.2025 14:26Вопрос тут не в синтаксисе, а в семантике языка.
arturdumchev Автор
18.01.2025 14:26Про макросы то же самое можно сказать. Приходишь на новый проект — не понятны ни синтаксис, ни семантика написанных макросов. Сперва надо с этим разбираться. Писали бы все всё одинаково, как в Go — не приходилось бы тратить на это время никому.
Wesha
18.01.2025 14:26Вы же не будете писать сразу на байткоде или на ассемблере?
(Характерным жестом поправляя очки на носу:) Не, нуачо, могу и тряхнуть стариной!
randomsimplenumber
18.01.2025 14:26А платить за подобные изыски кто-то готов? И, можно подумать, говнокодить на ассемблере сильно невозможнее чем на питоне.
santjagocorkez
18.01.2025 14:26То есть, если дождь не идет, или зонт при себе, никто никуда не идёт. Ждем, когда протухнут зонты и пойдет дождь. Весело там у вас.
dom1n1k
18.01.2025 14:26Я не шарю в Котлине, но вот это что-то странное:
data class User( val age: Int, val id: Int, val name: String, val surname: String, ) val (_, id) = getUser()
Серьезно?! Язык неявно преобразует структуру вида ключ-значение в упорядоченный кортеж? Но это же дичь, фундаментальный смысл ключей в том, чтобы не зависеть от их порядка.
Или что такое функция
getUser
, может там внутри какое-то преобразование, которое не такое уж неявное?arturdumchev Автор
18.01.2025 14:26Не, в
getUser
возвращение класса User. Дока.iroln
18.01.2025 14:26А во что распакуются name и surname?
Если написать аналогичный код в Python с NamedTuple, то будет простоtoo many values to unpack
и надо так, чтобы получить id через распаковку:class User(NamedTuple): age: int id: int name: str surname: str user = User(30, 1, 'John', 'Doe') _, id, *_ = user
В доке по ссылке нормальный пример кстати, там нет "лишних" полей, а у вас есть.
Arioch
18.01.2025 14:26Ошибка в том, что age и id разные типа данных, age измеряется в годах, а id безразмерный.
Но Java и Kotlin не умеют выражать физические типы. Если бы умели, то сразу бы словили ошибку типов, что duration нельзя неявно конвертировать в int и наоборот.
Правда, реальные типы так просто не помогут, если поменять местами name и surname... Тут уже только под-классы str создавать, но можно ли запретить Котлину неявную конвертацию между двумя под-классами str?..
iroln
18.01.2025 14:26Я понимаю в чём ошибка с точки зрения логики. age и id оба целые числа и если поменять местами поля в структуре, то получишь age вместо id. Я не понимаю как работает распаковка кортежа в этом случае. Строковые поля, которые есть в структуре, просто игнорируются или что? Тип переменной же выводится автоматически, там
var (_, id)
в левой части. Распаковываются только инты, а не вся структура? Тогда это ещё более всрато и не удовлетворяет правилу наименьшего удивления.Arioch
18.01.2025 14:26ну идея-то языка понятна...
Котлин вероятно ожидает, что все следующие разработчики будут только добавлять поля и только в конец DTO, и не будет удалять/менять поля - и тогда пожалуйста, вот ваше наименьшее удивление, ты добавил поле - а старый код работает без изменений и исправлений
правда эта фишка провоцирует использовать псевдо-кортежи для связывания модулей, а это уже.... Язык неявно преобразовывает DTO в кортежи, а это и впрямь отличная грабля. Все эти микро-фишки функциональные - кортежи и лямбды - отлично работают, пока умещаются на одном экране, но не в коем мере не для связки разных модулей между собой. Лично я бы вообще запрещал кортежам(tuples) быть видимыми вне своего модуля (файла исходников).
ну даже азбучное some-collection.filter(predicate-function) - отлично и наглядно, когда у тебя структура определена сразу над функцией предикатом, тут даже именованная функция не нужна, лямбда даже яснее будет. Но если определение в одном модуле, а именованный предикат в другом... то начинаются мучительные забеги по всем исходникам, а если ещё и совпадения имён в разных модулях и начинает "играть" порядок импортов, ой...
В принципе, это не первая идея, которая экономит время вот-прям-щас, но ценой большой жопы через пару лет. Я вот, так уж получилось, на последних работах разгребаю легаси. Одно зародилось в середине 90-х годов, другое - в середине 2000-х. Но обе построены вокруг паттерна супер-объект. Последнее к тому же в половине кода отказывается от типизации и все переменные делает типа OLE Variant. Так же проще, правда? Ну... писать простые куски конечно проще, но вот потом ловить и давить баги становится ой как хреново. Хотя в моменте - да, съэкономили и усилия, и память.
Но в промышленных ООП-языках раньше такие фичи давили и оптимизировали языки под нудно-надёжное программирование толпой винтиков на протяжение многих лет. А функциональщина, похоже, наоборот, оптимизируется под творца-гения, который как в Perl - write once read never, написал программу на лекции (очень быстро, не отнять), показал студентам и - забыл её сразу и навсегда.
kotlomoy
18.01.2025 14:26Ловушка через «destructuring»
Это частный случай проблемы кортежей и любых неименованных последовательностей типа списка аргументов, передаваемых в функцию.
Jijiki
18.01.2025 14:26спасибо за статью, очень понравилось что есть примеры и описание, интересно было бы посмотреть вообще как правильно или как надо, я тоже заметил что в языках много тонкостей, например в си++ в return (vec3)4*sin(10)+cos(1); можно возвращать, по началу было непривычно, потом подумал вроде логично если оператор с регионом памяти может работать(а может всё проще может это число пойдёт во все компоненты )) - подводных камней предостаточно в кодинге почти везде
Лиспоподобные языки тоже нравятся - хороший детокс от С/С++ и разгрузочная остановка )
diakin
18.01.2025 14:26Отстаньте вы уже от goto. Это как кому чего в руки попадет- дай дураку шар хрустальный, так он и лоб себе (и другим) расшибет.
arturdumchev Автор
18.01.2025 14:26Хочется, чтобы язык на работе защищал от коллег. Вот как с Golang и его философией, что каждая задача решается одним способом. Не нужно спорить, какой форматтер выбирать — есть только один. Не нужно просить убрать неиспользуемые vars — программа не скомпилируется. Не нужно разбираться с десятками подходов к concurrency — есть горутины. Не нужно думать, тут использовать for/forEach/while/times/repeat/sequence/range — есть только for.
Любую кодовую базу с Go открываешь в интернете — везде всё одинаково, не тратится время на разбор, что за магия происходит.
Правда, structured concurrency в Go нет, и на горутинах можно наворатить говнокода тоже. Но радует, что пространства для маневра (т.е. для говнокода) меньше.
На работе хочется договариваться о жестких рамках, а дома на пет-проектах можно делать что хочешь. Мне свои проекты нравится на Clojure писать, а на работе хотел бы писать на Go.
Вот также и с
goto
— не хочется поддерживать проекты, где у людей была возможность это использовать, но сам вне работы на каком-нибудьscheme
c удовольствием потрогаюcontinuation
.tolyanski
18.01.2025 14:26Ну всякие навороты могут быть полезны, например, задачах, где очень критична производительность вычислений. Например, использовать ли vector, map, ordered_map, queue, stack или еще чего? Или же например делать ли вот так или лучше так, - где меньше происходит дорогостоящих аллокаций памяти, из-за которых можно нарваться на жесткие тормоза?
Но конкретно в Go таких задач очень мало, тк этож бекенд, в котором все равно 80% времени тратится на ожидание ответа от внешней системы...
lightmaann
Искренне извиняюсь за иронию, но звучит забавно, что Вы начали замечать это, работая в Яндексе, который шлифует пятьюдесятью этапами собеседований на профпригодность.)
arturdumchev Автор
О, я даже помню, что написал своему бывшему коллеге из LinguaLeo, который тоже работал в Яндексе в прошлом. Диалог был на фейсбуке, который я удалил, скопировать не могу — расскажу, как помню.
— А у тебя на проекте в Я тоже был говнокод?
— Да, тот еще.
— А где-либо ты работал, чтобы код был нормальный?
— Нет.
Я уже 10 лет в разработке. Такого, чтобы прийти на проект, которые поддерживается 3+ лет и чтобы кодовая база была хорошей, — не было ни разу. Смотрел на код друзей, работающих в других компаниях. Самое-самое ужасное, что видел — размер функции main в java-приложении на несколько десятков тысяч строк кода.
Krawler
Я думаю что никакого заговора нет, а это следствие естественной деградации кодовой базы в условиях недостатка времени (кто видел бизнес которому фича нужна не вчера, поднимите руки) и недостаточной аналитики (не обязательно прям работы СА, а просто малого времени на анализ в целом)
arturdumchev Автор
Да, просто стилистику написния статьи такую выбрал. Иногда дело в том, что требования меняются, а времени мало. Иногда в том, что текучка, и новые разработчики пишут код, не зная деталей. Но бывает и так, что плохой код пишется сразу — на это повлиять уже можно.
Krawler
Совершенно верно. Или выгорание текущих сотрудников и нежелание бизнеса это решать. Это уже в сторону "нам нужен человек, способный управлять звездолетом в одиночку" Но это анриал, и лично я считаю, что как раз-таки повальная экономия на специалистах (давайте возьмем одного фуллстека вместо бэк + фронт) вкупе с нехваткой времени и выгоранием текущих и приводит к проблеме.
Насчет же сознательного саботажа - мне кажется, это уже крайняя форма, когда тебя настолько все заколебало, что ты злостно потирая ручки хочешь что-то испортить. Но мне такое не встречалось
(Вспоминается бородатый анекдот про то, что лучшие хакеры - злые админы)
Зато встречалось нежелание заботиться о сотрудниках. Про wellbeing у нас обычно говорят только топы рынка, хотя эмоционально-психическое состояние сотрудников безумно важно и грамотная помощь и поддержка может сильно сократить проблемы
arturdumchev Автор
Если что, я не имел в виду, когда писал статью, что на самом деле есть люди, специально разрушающие код. Это скорее троллинг над теми, кто пишет плохой код по тем или иным причинам сразу, например, от того, что опыта нет.
А под фразой “Но бывает и так, что плохой код пишется сразу — на это повлиять уже можно.“ имел в виду, что плохой код (написанный плохо по неопытности или в торопях) можно на codereview найти и устранить. Можно увеличить количество ревьюеров. Можно вести доклады команды, чтобы все росли, проговаривая ошибки, или процесс, подразумевающий защиту архитектуры до написания кода. Или pair programming.
Krawler
pair programming - это очень крутая штука, которая хорошо работает. На текущей моей работе мы пользуемся этим и это позволяет хорошо повысить качество кода
Есть даже развитие этой техники - mob programming, это то же парное, но в роли Навигаторов может быть несколько человек, которые обсуждают проблемы до написания кода.
Про доклады - интересная идея, которая мне очень близка, это внутренние митапы. Но это довольно тяжело обосновать бывает
Однако, есть еще и другая точка зрения Сводится все к тому, что т.к. теперь двое работают над одной задачей - то это, якобы, неэффективно расходует ресурсы (ведь можно им выделить каждому по задаче)
Контраргумент: продуманная архитектура сейчас сэкономит время в будущем, за счет того, что новые фичи на нее будут ложиться легче и интуитивнее
У моих комментариев также не было цели заставить оправдываться. Скорее углубить и развить тему. Это дискуссия а не спор.
P.S. Я вообще не разделяю никаких теорий заговора
vonabarak
Это важная ремарка.
Как-то раз попадал в ситуацию, когда человек тоже начал рассказывать о всемогущей организации, которая устраивает саботаж. Я сначала решил подыграть ему, но через несколько минут стал подозревать, что он не прикалывается, а абсолютно серьёзно в это верит. К счастью, когда я уже совершенно точно понял, что это была не шутка, то мы уже почти приехали, поэтому неловкое молчание продлилось недолго, и больше мне этот таксист никогда не попадался.
Wesha
Он представлял опасность, поэтому Организация его устранила!
alamat42
Иронично, что эти самые разрушители корпораций из статьи - это обычно эффективный менеджмент самих корпораций, который не даёт разработчикам времени на рефакторинг и анализ.
arturdumchev Автор
У меня были примеры в статье, которые не получится оправдать спешкой. Например, логика через try-catch (не связанная с ошибками), или неидиоматичное использование ключевых слов языка (apply), или
!isDisabled()==false
, или добавление мета-программирования, которое ничего не дает, кроме магии.Но что-то действительно может возникнуть из-за спешки.
Wesha
(Презрительно сплёвывая:) Черпак!
arturdumchev Автор
Да не, я только к тому, что повидать успел и не беру это из воздуха
Wesha
Да не спорю, я ж шуткую. Просто «я ж, Петька, постарше тебя буду» ©
PikNic
Linux, U-Boot, скучаю по ним, кодовая база очень качественная. Сейчас ковыряю FreeBSD, даже близко не дотягивает. Например, не ожидаешь увидеть в ядре достаточно известной ОС куски кода в функциях, в которые по условиям в принципе попасть невозможно.
Wesha
Фряха — серверная система, десктоп в ней, можно сказать, по недоразумению.
Ratenti
Какая отношение это имеет к теме?
Wesha
К какой из них?