Pull to refresh
30
0

User

Send message

Робот не может причинить вред человеку или своим бездействием допустить, чтобы человеку был причинён вред.

(c) Законы Робототехники Айзека Азимова

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

Да, по процессу на вкладку сильно затормозило развитие лисы, но за этим будущее, и это нужно было сделать.

В плане пользовательского интерфейса: что бы быстро отличать от хрома воспользовался такой настройкой UI:

URL снизу, очень необычное, но удобное нововведение
URL снизу, очень необычное, но удобное нововведение

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

Firefox вполне бодро развивается, а здоровая конкуренция идёт всем на пользу.

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

Чтение исходного кода порой сопастовимо с чтением хорошего детектива! 

Все так, однако:

  • действительно хороший код встречается крайне редко;

  • исправление либо новый функционал почти всегда ожидается в ближайшее время;

  • на чтение всего кода может просто не хватить жизни;

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

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

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

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

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

Какие это дает плюсы? За пару минут можно написать новое правило. За пару минут можно исправить ошибку. Все быстро и приятно.

Нравится читать код как детектив? Добро пожаловать!

Я бы вовсе вложенные if‘ы запретил (а может даже и else):

function main() {
    if (a)
        return calc(a);

    return 0;
}

function calc(a) {
    if (a)
        return 5;

    return 7;
}

Вместо этого:

  • early return;

  • отсутствие фигурных скобок;

  • разбиение алгоритма на компактные блоки с внятными именами;

Это не так гибко, зато очень читаемо.

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

  • Наличие false positives;

  • Огромное количество сообщений об ошибках;

  • Отсутствие состояние прогресса (не понятно когда завершится проверка)

В качестве решения для JavaScript использую:

  • ESLint настроенный исправлять, все что он находит;

  • 🐊Putout, который сообщает только о том, что может исправить сам;

Конечно, это не спасёт от ошибки вроде ParadoxicalCondition, описанной в статье, но с этим не плохо справляются тесты + coverage.

Очень компактный и интересный язык. Не понятно только для чего по-умолчанию делать переменные глобальными, а локальность вводить оператором local.

Ведь в JavaScript тоже так было.

First, strict mode makes it impossible to accidentally create global variables. In normal JavaScript mistyping a variable in an assignment creates a new property on the global object and continues to "work" (although future failure is possible: likely, in modern JavaScript). Assignments, which would accidentally create global variables, instead throw an error in strict mode.

(c) MDN

Но с появлением strict modeпоявилась возможность сделать код надежнее благодаря конструкции 'use strict'.

А теперь и эта конструкция не нужно: строгий режим включён по-умолчанию в EcmaScriptмодулях:

ECMAScript 2015 introduced JavaScript modules and therefore a 3rd way to enter strict mode.  The entire contents of JavaScript modules are automatically in strict mode, with no statement needed to initiate it.

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

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

(c) Википедия

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

В плане эргономичности чего только стоят:

loop {
    break;
}

Вместо:

while(true) {
    break;
}

Все операторы - выражения, а последнее выражение в функции, если оно не содержит ";" - возвращается.

Вишенка на торте: объявление переменных.

  • let - не меняет значение;

  • let mut - меняет;

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

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

Благодарю за развёрнутый ответ.

Автоматическими изменениями кода занимаются инструменты, осуществляющие рефакторинг или форматирование кода.

Рефакторинг - это плановое изменения кода по обозначенному маршруту в рамках конкретного проекта.

Форматирование - это визуальное оформление кода: отступы и точки с запятыми.

Я же говорю об автоматическом исправлении проблемных мест в соответствии с лучшими правилами построения качественного кода.

Пример из статьи очень не плохой кандидат для автоматизации:

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

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

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

Какие случаи вы считаете простыми? Наверняка те, с которыми научились работать, и те которые начали автоматизировать. Начало в любом случае будет простым. Но из этого простого решения, постепенно начнут складыватся более сложные. К примеру в 🐊Putout есть возможность перевода CommonJS в ESM и наоборот.

В результате, эта функциональность будет столь ограничена, что лучше вообще её не делать.

Тут вы делаете вывод из собственного утверждения. А ведь простой функционал - это быстрый MVP и прямая польза.

Тем более всё равно слишком велик риск неправильного изменения кода

Риск есть всегда, однако есть и риск успешного приминения трансформаций кода, и тогда ошибки можно будет не только найти но и применить исправление. А неправильные изменения кода должны ловить тесты и компилятор.

> и человек обязан участвовать в этом процессе.

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

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

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

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

  • ограничением применимости;

  • узкой областью действия;

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

Возьмём, к примеру return, как верно отметили в комментариях выше, это тот же goto, однако конкретнее:

  • прыжки возможны лишь в конец функции;

  • места переходов достаются с вершины стека вызовов;

for и for-of, определяет правила игры для goto: либо счётчик, либо границы массива.

Функции, промисы, конструкторы и методы классов: goto формирующие стек-вызовов.

Таким образом все можно выразить с помощью goto, это такой себе jmp из мира ассемблера (где тоже есть, более мощные работающие со стеком вызовов, альтернативы:call и ret).

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

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

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

  • Большое количество ложных срабатываний;

  • Правила, которые нужно выключить из-за ложных срабатываний очень не просто найти по цифровому коду, по-типу JS-1234, для JavaScript;

  • Нет исправлений, только рекомендации, порой очень странные, например если слово token встречается в тестах, то это security issue;

Все эти проблемы решаются с помощью 🐊Putout:

  • Все что находит - исправляет;

  • Содержит человеко-понятные названия правил: remove-unused-variables либо declare-undefined-variables, для удаления не используемых, либо объявления не объявленных переменных;

  • Кроме рекомендаций есть конкретные преобразования, вместо отнимающих время ложных срабатываний;

🐊Putout проверяет и исправляет сам себя. Его использую, наряду с несколькими другими анализаторами. Слишком много статического анализа не бывает.

Это Monaco Editor, он поддерживает TS из коробки.

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

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

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

Гораздо важнее правильно называть переменные и инициализировать их при объявлении. Если ещё и константы использовать, то проблемы с типами сведутся к нулю.

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

Так они пришли к одному ответу обменявшись парой фраз, вряд ли они усложняли все с каждой фразой, скорее к общему знаменателю шли, понятному и очевидному, Keep it simply stupid.

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

a + b = c
a * b = d

И 4-ре неизвестных. Первый мудрец отметил, что не знает чисел, и второй сказал, что знал это. Конечно, пар чисел очень много, значит нужно упрощать:

a + a = c
a * a = d

Теперь у нас 3 неизвестных, два уравнения, и допущение, что султан загадал одинаковые числа. Дальше первый мудрец сказал, что теперь он знает, эти числа, то есть допустил, что c и d равны, тогда:

Решение

a + a = a * a;

2 = a*a/a

a = 2

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

Читая существующие ответы возникает вопрос: для чего все эти усложнения, если мудрость в простоте.

На самом деле это хороший совет с точки зрения промышленной разработки, и вот почему:

  • проверяется надёжность написанных тестов: проверяют ли они хоть что-то;

  • когда код написан по TDD, тесты, даже при отсутствии кода будут вести по цепочке реализации, поскольку качественные тесты - это знания о работе системы;

  • программа набирается сразу после прочтения задания, а мы включаемся в поток направляя мысли в след за мышечной памятью;

  • задействуя 20% усилий мы получаем 80% результата, и я сейчас не столько о 15 баллах, сколько о том, что за пару секунд мы получаем рабочий прототип решения;

Абсолютно с вами согласен! Не буду углубляться в терминологию, расскажу как я понимаю DDD.

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

  • если это магазин - то должна быть карточка товара, пользователь, расчет;

  • если это парикмахерская - то запись, мастера, и т.д.

То есть, функционал ограниченный конкретной областью. Абсолютно противоположный вариант, это общие названия папок, в которых собраны все сущности сразу (actions, components и т.д.). Это был пример простыми словами. Я участвовал в 5-ти проектах на разных работах, где научился DDD пропустив через себя, и с удовольствием применяю.

Теперь ближе к практике. Putout - линтер, который пишется по DDD. Его структура очень проста, и в то же время конкретна.

Есть движки:

  • Парсер (строка -> AST, AST -> строка)

  • Загрузчик (загружает плагины)

  • Исполнитель (исполняет плагины)

  • Процессор (загружает линтеры разных типов файлов, а так же достает JavaScript и прогоняет через предыдущие 3)

Движок Процессор поддерживает следующие процессоры:

  • markdown

  • json

  • yaml

  • gitignore

  • и так далее

А еще есть плагины и форматтеры. Вот и все DDD. Главное помнить, что сущности должны быть разделены на конкретные области, лежать в конкретных местах не покидая их пределов, и не размазываясь по всем папкам сразу. Таким образом обеспечивается low in coupling and high in cohesion, то есть упрощается поддержка приложения.

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

Внес правки в соответствии с вашими предложениями, благодарю за проделанную работу! У меня числа получились другими, возможно дело было в конкретной версии node.js. Рад, что вы не поленились клонировать репозиторий, кстати, недавно вышел Putout v18, в котором я немного переосмыслил API процессоров, сделав его более наглядным.


Буду рад любым пулл риквестам от вас, которые могут улучшить быстродействие :)!

Очень хороший аргумент! Рассмотрим такой вариант.
Открываем processor.js:


function process(runners) {
    const files = getFiles(runners);
    const results = lintFiles(files);

    return results;
}

Видим, что у нас две операции, которые происходят последовательно. Вначале getFiles, а потом lintFiles, при этом getFiles зависит только от значения runners, переданных из вне. Тут происходит разделение сущностей на уровне выполняемого действия согласно Философии UNIX:


Пишите программы, которые делают что-то одно и делают это хорошо

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


function getFiles(runners) {
    const files = [];

    for (const run of runners) {
        files.push(...run());
    }

    return files;
}

Либо lintFiles:


function lintFiles(files) {
    const results = [];

    for (const file of files) {
        results.push(processFile(file));
    }

    return results;
}

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


Так же, если нужно будет поменять способ получения списка файлов, либо добавить еще несколько видов обработок файлов — достаточно будет добавить рядом функцию и ее вызов.


Теперь для понимания связи между двумя циклами я должен просмотреть отдельно функции и при внесении изменений редактировать их отдельно друг от друга.

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

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

Внёс уточнения в статью, спасибо большое за детальный разбор!

Ну я указал что вам нужно найти. Можете начать с Википедии или курса лекций Яндекса (сложность первые две лекции)

Спасибо за ссылку на википедию, я ознакомился и не нашел ничего, что бы подтверждало ваши слова о том, что:


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

Звучит это очень загадочно, как и ваши примеры кода.


Давайте отбросим рассуждения про push и concat. Разница там будет очень маленькой т.к. v8 оптимизирует push и я не думаю что будет разница заметная на тестах. Я писал про алгоритмическую сложность и сделал пометку про "зависит от реализации". За счёт дополнительной памяти можно сделать push в среднем линейным, если я правильно помню (это разбирается во второй лекции курса ссылку на который я дал).

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


За счёт дополнительной памяти можно сделать push в среднем линейным, если я правильно помню (это разбирается во второй лекции курса ссылку на который я дал).

Спасибо, мне хватило ссылки на википедию.


Давайте вернёмся к главной теме. Ваши алгоритмы до и после оптимизации идентичны. Это изменение не приводит к изменению алгоритмической сложности. Вы можете что-то на это возразить?

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


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

И где-то это даже может быть очень важно, но цель статьи, в том что бы показать, что оптимизация важна и она может улучшать качество кода.

Information

Rating
Does not participate
Registered
Activity