Как стать автором
Обновить

Комментарии 38

Последний хоть читаемый. Ещё бы в нём последнюю строчку разбить на if, прошёл бы код-ревью у меня.

А как вы определяете читаемость?

Гипотетически, если бы мне действительно надо было проходить ревью у вас, как бы я мог понять, достаточно ли читаемо написал?

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

Поэтому в некоторых коллективах MR принимается по факту наличия одобрения от нескольких ревьюеров

По-моему, сейчас вы смешиваете "понятность" и "читаемость"

Понятность субъективна, потому что зависит от опыта читающего.

Читаемость же, вполне себе объективная характеристика

  • на запихиваем 100500 действий в одну строку

  • ограничиваем предельный размер строки

  • отделяем пустыми строками важные части

  • предпочитаем короткие имена вместо ОоченьПодробныхНоСлишкомПримитивных

  • придерживаемся единообразия в форматировании

У вас "очипятка" перед 100500. И оченьДлинныеИмена лучше верблюдом писать, там читаемей.

Есть такая тема - cognitive overload. Его очень просто добиться. Делаем много условий, пишем их в одной строчке, используем тернарные операции, хорошо бы вложенные.
Добавляем пару каскадов (типа LINQ в дотнете). И делаем всё в одном методе.
Вуаля! Теперь любой, кроме автора, а через пару месяцев и автор, потратит пару минут вместо пары секунд на то, чтобы понять, что там происходит

Лучший код — это ненаписанный код

Поэтому чем меньше кода, тем больше лучшего кода

Ассемблер рулит

А вы не сравнивали скорость работы и потребление памяти для подходов со stream и при использовании циклов и кодирования ручками?

Нет, никакой "технической оценки" вариантов не делали.

Прежде чем пытаться ответить на вопрос о том, какой код лучше, следует определить критерии оценки кода.

Тут есть ловушка

Дело в том, что "критерии лучшести" часто выражаются абстрактными словами.

Допустим, мы считаем, что хороший код должен быть выразительным и поддерживаемым (и работать правильно, само собой)

А что такое выразительность и поддерживаемость?

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

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

На мой взгляд, ваши критерии

  • позволяет как можно быстрее вносить изменения сохраняя при этом минимально необходимый уровень качества

  • соответствие реального поведения программы ожидаемому поведению описанному в ТЗ

все равно довольно абстрактны.

Сейчас я не понимаю как их можно свести к числам.

Если вам не трудно, можете провести оценку пары вариантов, например, первого и пятого по своей схеме?

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

Если нужны какие-то уточнения по ТЗ, или другого плана, то я готов отвечать на вопросы.

"Если вам не трудно, можете провести оценку пары вариантов, например, первого и пятого по своей схеме? " - не трудно) Мне самому интересна эта тема.

"позволяет как можно быстрее вносить изменения сохраняя при этом минимально необходимый уровень качества" - здесь два критерия, а не один:
1. позволяет вносить изменения как можно быстрее
2. соответствие реального поведения программы ожидаемому поведению описанному в ТЗ

Первый критерий оценивается просто: сколько времени у вас ушло на написание каждого варианта, сколько времени уйдет на внесение изменений, сколько времени ушло на отладку, и главное - сколько времени ушло на все суммарно. Заметьте, тут в первую очередь оцениваются именно Ваши затраты времени.
Лично у меня пятый вариант занял меньше всего времени, чтобы разобраться в нем. Поэтому с точки зрения этого критерия, ИМХО, он лучший.

Второй критерий посложнее: на кейсы из ТЗ пишутся тесты. Чтобы варианты можно было сравнить между собой - тесты пишутся по методу черного ящика. В идеале тесты пишутся другим человеком. Чем больше тестов успешно проходит вариант, тем он лучше. Чем меньше багов связанных с этим участком кода было выявленно в дальнейшем - тем лучше.

не трудно) Мне самому интересна эта тема.

Отлично же :) Тогда давайте я попробую применить ваши критерии.

Первый вариант я написал за 5 минут.
Чтобы написать пятый вариант, понадобилось 10 минут.

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

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

Сколько времени уйдет на внесение изменений в будущем не понятно. Ведь мы не знаем какого рода это будут изменения и кто их будет вносить.

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

На решение написано 5 тестов:

  • 3 теста на случай когда не передали какой-то из параметров

  • 1 тест когда пользователь не имеет доступа и получает отлуп

  • 1 тест когда все хорошо и доступ есть

Оба варианта успешно проходят все тесты.

Как понять какой вариант лучше? Первый, потому что потребовал меньше времени?

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

"Как понять какой вариант лучше? Первый, потому что потребовал меньше времени? " - выходит, что первый вариант самый лучший.

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

Если изменятся или добавятся требования, то оценка уточняется (к результату старой оценки добавляются результаты новой оценки).

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

Для того, чтобы понять что и куда двигается, достаточно понять непосредственно что вы делаете и для кого, а точнее для какого пользователя вы это делаете.

Бывает, что софт проваливается только из-за того, что держал пользователя за идиота. Аналогично, когда функционал перенасыщен настолько, что основные функции софт уже выполнять не может.

И какого-то особого опыта там не требуется. Его вообщем и не определишь. Погружение в проект давно известная дилемма в управление.

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

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

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

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

Тут есть забавный нюанс.

Характеристики "понятно" и "академически", сами по себе, довольно абстрактны и каждый человек вкладывает в них какой-то свой смысл.

Когда я обсуждал статью с коллегами внутри компании, то получалась такая ситуация
* "простым" считали либо первый, либо пятый
* "академическим" считали либо второй, либо третий

Вот лично для вас, на пальцах, что значит "просто", а что "академически"?

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

А "простой" - это тот код, который читается без запинок в понимании. Но это не всегда равно эффективный, конечно же.

До вашего пояснения я думал что между "понятно" и "академически" существует дихотомия.

Теперь я уже не уверен. В вашей "системе ценностей" код может быть одновременно и "понятным" и "академическим" (варианты 1 и 5), или я что-то понял не так?

Если вам не трудно, могли бы в классифицировать примеры из статьи по системе "понятно"/"академически" с комментариями почему именно так?

Характеристики "понятно" и "академически", сами по себе, довольно абстрактны и каждый человек вкладывает в них какой-то свой смысл.

Как всегда говорят классики: "Программист в такой момент руководствуется только своим чувством прекрасного".

Не важно как пишет программист: разляписто или элегантно, в одну строку или со структурой, применяя хакерские приемы или не применяя. Извечно все спрашивают только два вопроса: Что вы делаете? Зачем вы это делаете?

Ваше "Как это делают?" скорее риторический вопрос, чем математическое уравнение. Именно это и подчеркивалось в грубой, но меткой формулировке "Лучший код - это ненаписанный код", говоря о том, что часто лучшее решение там, где не требуются лишние проверки и формальности.

Я не понял, что вы хотели сказать :(

Не важно, как написан код, главное чтоб задача решалась?

Вообще не надо писать код, надо находить какие-то другие пути?

Не важно, как написан код, главное чтоб задача решалась?

А в чем тогда цель code review? Внушать программистам волю team lead-а, которому не хватает ума, чтобы набраться авторитета, а назначить из команды мозгов уже менеджерам не хватило? Заражать предрассудками о неком "идеальном чистом коде", попутно вырабатавыя лишь комплекс неполноценности?

Именно такие вопросы приходят на ум, после всего что я тут прочитал.

А если вы хотите узнать о настоящей деловой коммуникации, то советую почитать.

Вообще не надо писать код, надо находить какие-то другие пути?

Нет, я ответ дал в последнем абзаце. Не вежливо, знаете, отвечать человеку не дочитав его сообщения.

Я так скажу. Вообще в программирование нет понятия "эстетики кода". Классики ещё до вашего рождения здесь всё расписали. Мы все когда-то проходили через этап комплекса перфекционизма, когда только начинали программировать, но с опытом приходит понимание и творческое осознание. Именно оно, к слову, отличает начинающего от специалиста, а не какие-то там железные принципы и тем более предрассудки. Михаил Флёнов в своих книгах "Программирование глазами хакера" отмечал это разницей между "хакером" и "крекером" (от англ. crack - "взломать") в самой первой главе.

Каждый из вариантов - говнокод

  1. Нейминг

  2. Применение ООП не в том месте и ещё и с логикой

  3. Перепутаны зависимости.

  4. Сущность делает не свое дело и варинципе не нужна.

  5. Нарушение принципоsolid

Не используйте ООП внутри архитектуры, тем более наследование

Нейминг сущностей важнее кода.

Логика и данные должны быть отделены.

Минимизировать зависимости.

Сущность использует сеть для запроса, следовательно она должна обратиться к сервису запросов. Значит у нее очень большая власть. Но как только вы правильно назовёте класс, то станет очевидным что эта сущность в принципе не нужна. Ваша тезис оказался верным.

Мы делаем аддон для Confluence Cloud на основе Play Framework

Не заметил. Тогда ооп применимо в таких местах. Архитектура фреймворков отличается

Но все равно Action - объект который не вписывается в иерархию.

У вас уже есть PermissionService. Это он дожен вызывать запросы, и в нем должна быть бизнес-логика. Хотя непонятно назначение PermissionService, разве запросов на доступ будет много? Но очевидно что он зависит от более общего RequestService. ViewModel страницы вызывает уже IRequestService и заполняет Model, про сервер и запросы она ничего не знает.

Сущность Action - не имеет смысла. Количество запросов будет все время расти и изменятся, наследование этому помешает. Тут можно использовать паттерн стратегия, когда увеличется количество.

А какой принцип солид нарушен в 5 пункте?

Вариант 1

  • Используется Optional.get значит уже что-то неправильное происходит, либо вам не нужны Optional. 1 WTF.

  • Вложенные IF когда это можно избежать. 1 WTF

  • thenComposeAsync да еще и в commonPool! Тут вопрос насколько автор действительно хочет следующий stage запускать всегда в другом потоке из commonPool, а не в том же потоке в каком логика canViewPage. permissionService тоже ведь асинхронный и тоже что-то запускает в другом потоке. Так зачем эти прыжки из потока в поток? Тут 3 WTF.

Вариант 2

  • permissionService.canViewPage возвращает Optional<CompletionStage<Boolean>> что-ли? Может и не возвратить stage? Тут ошибка дизайна какая-то ради flatMap. Сразу 10 WTF

Вариант 3

  • Function3 - ошибка дизайна, надо создать класс с нормальным именем, вроде SecurityRequest, в котором будет 3 поля и он будет передаваться в canViewPage. 10 WTF

Вариант 4

  • Tuple - 10 WTF, то же самое. Никаких Tuple, если это не математические вычисления, быть не может.

Вариант 5

  • orElse(null) как раз и говорит, что Optional, по крайней мере тут в API, не нужен. На уровне синтаксиса java не смогли сделать как в Kotlin, дали костыль - Optional.

Еще нет логов.

Там static import на CompletableFuture? Очень сложно ревьюить. Имхо это оправдано только в тестах, где очень много Mockito и все понимают, что это static import.

public CompletionStage<Result> call(Request request) {
    ACHost acHost = getAcHost(request);
    if (acHost == null) {
        log.debug("Request(id={}).acHost is empty: unauthorized", request.getId());
        return CompletableFuture.completedFuture(unauthorized());
    }

    Account account = getAccount(request);
    if (account == null) {
        log.debug("Request(id={}).account is empty: unauthorized", request.getId());
        return CompletableFuture.completedFuture(unauthorized());
    }

    Page page = getPage(request);
    if (page == null) {
        log.debug("Request(id={}).page is empty: unauthorized", request.getId());
        return CompletableFuture.completedFuture(unauthorized());
    }

    return startAsync(start -> start
            .thenCompose(unused -> {
                log.debug("Request(id={}) processing: acHost={}, account={}, page={}",
                        request.getId(),
                        acHost,
                        account,
                        page
                );
                return permissionService.canViewPage(acHost, account, page);
            }).thenCompose(canView -> {
                if (canView) {
                    log.debug("Request(id={}) authorized", request.getId());
                    return delegate.call(request);
                } else {
                    log.debug("Request(id={}) forbidden", request.getId());
                    return CompletableFuture.completedFuture(forbidden());
                }
            })
    );
}

startAsync - это такая штука которая лямбду всегда зпускает в другом потоке. Вначале создаем цепочку stage, потом запускаем в каком-нибудь execution service.

public CompletionStage<Result> startAsync(Function<CompletionStage<Void>, CompletionStage<Result>> closure) {
    CompletableFuture<Void> start = new CompletableFuture<>();
    CompletionStage<Result> cs = closure.apply(start);
    start.completeAsync(() -> null, es);
    return cs;
}

Вот ради таких комментариев я эту статью и написал. Отдельное спасибо за свой вариант кода.

Правильно ли я понимаю, что "победил" вариант 5 со счетом 1 WTF?

И еще момент.

Как предлагается решать вопросы субъективности WTF-метрики?
Лично знаю людей, которые не считают Optional.get проблемой с мотивацией вида "раз метод существует и не помечен устаревшим, то можно пользоваться"

Да, вариант 5 самый читабельный.

Код пишется 1 раз, а читается много, поэтому важно, чтобы написано было так, чтобы его легко было читать, отлаживать, тестировать, расширять и переиспользовать. Для этого придумали Naming Convention, Code Style, комментарии, логирование, а так же подходы solid, dry, kiss, yagni, ...

По поводу Option.get, если делать правильно, то get вызывать не нужно никогда, но код превращается в функциональный, и кто-то считает это хуже Option.get, хотя я так не считаю. Если с Option делать и не писать все в 1 строку, то получится вполне читабельный код на функциональном языке.

public CompletionStage<Result> call(Request request) {
    return getAcHost(request).map(acHost -> {
        // acHost is set
        return getAccount(request).map(account -> {
            // account is set
            return getPage(request).map(page -> {
                // page is set
                return permissionService.canViewPage(acHost, account, page).thenCompose(canView -> {
                    if (canView) {
                        // authorized
                        return delegate.call(request);
                    } else {
                        return completedFuture(forbidden());
                    }
                });
                // page is not set
            }).orElse(completedFuture(unauthorized()));
            // account is not set
        }).orElse(completedFuture(unauthorized()));
        // acHost is not set
    }).orElse(completedFuture(unauthorized()));
}

От субьективности WTFs/minute никуда не уйти, даже оценки в школе мало коррелируют со знаниями. Есть книга Clean Code - одна из обязательных книг для программиста.

Извините, но это не читабельный код. Особенно если каждая вторая строчка содержит комментарий, чтобы как-то это дочитать это до конца...

А где вариант обернуть permission service в сущность вроде RequestPermissions, которая по реквесту будет говорить да/нет, чтобы не дублировать логику во всех экшонах. Почему бы не свести использование этого RequestPermission в одно место, в базовом классе например, или через композицию в виде класса CheckedAction, получающего RequestPermission и Action.

Так ведь именно это и сделано.

PermissionService используется только в одном месте, в Action

Этот Action силами фреймворка вызывется прежде чем пустить запрос к контроллеру

Контроллеры на которых такая проверка нужна просто помечаются аннотацией RequireAccessToPage

Или я неправильно вас понял?

Вариант шесть. Автор начинайте с определения. Договорившись об значение терминов, а ешё лучше приведя академическую формулировку из первоисточника, вы решите 90% проблемы.

Так я и пытаюсь договориться об определении :)

Есть задача и есть 5 вариантов решения (ситуация в статье)

Нужно взять самый качественный/лучший/хороший вариант.

И, внезапно, выясняется, что 3 человека выбирая самый качественный/лучший/хороший вариант взяли разные варианты.

Очевидно, что понятие "качественный" у каждого из них свое. Но как понять, из чего оно складывается?

Я решил поспрашивать по-больше людей, чтоб оценить критерии качества в крупную клетку.

Например, что вы думаете о предложенных вариантах?
Какой из них предпочли бы видеть у себя в коде?
Какой видеть не хотели бы?

Есть задача и есть 5 вариантов решения (ситуация в статье)

А что если скажу, что все ваши 5 вариантов - это полное гавное, понятное только вам и адептам Java, но совершенно не понятное никому-либо прочитавшему это?

Вы хотя бы знаете что такое комментарии в исходнике?!

Нужно взять самый качественный/лучший/хороший вариант.

И, внезапно, выясняется, что 3 человека выбирая самый качественный/лучший/хороший вариант взяли разные варианты.

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

На месте ваших коллег я бы сразу спросил: А нахрена там эта проверка? Лучше тогда сделать обработку ошибки. И засунул функцию в класс-предок, так как у меня одна функциональность уходит в три разных класса.

Очевидно, что понятие "качественный" у каждого из них свое. Но как понять, из чего оно складывается?

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

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории