Обновить

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

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

Но много краевых проблем когда будет куча варнингов по именовани или надо сильно усложнять код. Например случай когда ищем пару объектов из базы и потом с ними чтото делаем - оба должны быть в одном контексте для работы и как они тогда должны именоваться? Туда же чтото более сложное по физ/мат вычислениям где имя подфункции и название переменной связаны условно в рамках формул.

Вы правы для общего языка. Но данный паттерн будет применять исключительно для подмножества C++, который находится в разработке. Этот DSL(над С++) запрещает проблемные паттерны (общие имена, синонимы), требует явных структур вместо кортежей, и ограничивает область применения (не для мат. вычислений). Хотя даже для кортежей данное правило применяется свободно, но только в данном случае я разбирал конкретно семантику исключительно C++.

Вот как звучит правило для картежей:

Для картежей правило наименование становится тем же, и применяется к каждому аргументу

sender, recipient = find_sender_and_recipient()

# Придётся писать:
def find_sender_and_recipient() {
    user1, user2 = find_users()  # внутри можно
    sender = user1
    recipient = user2
    return sender, recipient
}

В данном случае sender, recipient и return sender, recipient совпадают. Правило применяются. Если вы имели ввиду что-то другое, уточните вопрос :)

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

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

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

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

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

Если я не ошибаюсь, то это есть в статье:

Данная идея интересна как исследовательская концепция и могла бы быть реализована в:

  • Доменно-специфичных языках (DSL) — где строгость именования критически важна

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

  • Экспериментальных языках

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

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

В моём DSL варнинги по именованию — это часть языка. Система заставляет явно описывать семантику через имена. Если получается 'куча варнингов' — значит, в коде было 'куча неявностей'.

так.
У меня 2 юзера и я хочу сравнить их айди.

// Единственно правильный вызов:
auto user_id = get_user_id(user1) // ✓

а второй как?
auto user_id2 = get_user_id(user2) // нарушит твою конвенцию имен

А если он не юзер, а админ, и я хочу это подчеркнуть?

Можно использовать кортежи: К примеру

auto [user1_id, user2_id] = get_user_ids(user1, user2);  // Функция возвращает кортеж

Правило остаётся, но есть контролируемые исключения для реальных сценариев.

И мы усложняем код только для того, чтобы переменные были фиксированные. Убрали сложность в одном месте, но добавили в другом (и, ИМХО, больше).

а что если надо так:

auto cur_user_id = get_user_id(get_cur_user());
auto cur_group = find_group_for_id(cur_user_id);
auto creator_id = get_user_id(cur_group.creator);
auto creators_group = find_group_for_id(creator_id);
for(auto other_user: cur_group.users)
{ 
  other_id = get_user_id(other_user);
  ...//далее в коде используются все предыдущие id, например:
  if (other_id == cur_user_id)
    continue;
  ...

Есть варианты:

<sarcasm on>

  • С++-style преобразования name_cast

auto cur_user_id = name_cast<cur_user_id>(get_user_id(get_cur_user()));
  • Дополнительные функции для преобразования:

auto get_cur_user_id(auto user_id) { ... }
auto cur_user_id = get_cur_user_id(get_user_id(get_cur_user()));
  • Введение неявных преобразований имён:

user_id может быть преобразован в

  1. <прилагательное>_user_id

  2. <числительное>_user_id

то есть разрешены конверсии user_id => current_user_id, second_user_id, other_user_id, power_user_id

а такие конверсии запрещены user_id => price_id, mail_and_user_id

<sarcasm off>

Очень интересная задумка

user_id может быть преобразован в
<прилагательное>_user_id

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

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

Допустим я всё именую в snake_case, а автор очень нужной мне библиотеки - в camelCase, а остальные библиотеки тоже используют snake_case, как предполагается такие проблемы решать?

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

Просто это довольно странно. Нужно либо всех обязать использовать какой-то один регистр, либо дать возможность переопределить имена. Иначе, очень вероятно, что люди просто забьют и будут использовать библиотеку с camelCase именами в проекте с преимущественно snake_case именами. Обычно даже строгих проверок делать не надо, достаточно просто конвенции прописать. В тех же ruby и go чаще соблюдают snake_case и camelCase соответственно, хотя ни интерпретатор в случае ruby, ни компилятор в случае go никак этому не препятсвует.

Абсолютно с вами согласен. Но я решил на данный момент отталкиваться от абсолютной строгости. Я не решил вводить "полумеры" в начале пути, потому что пришлось бы привести овер дофига пример/контраргументов. Эти полумеры в начале пути я посчитал избыточными. Я не против развить эту идею. Данная концепция не приватизирована каким-либо образом

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

Я больше верю в обозначение, какой регистр более предпочтительный: snake_case или camelCase, как в решение проблемы. Переопределение сложно тем, что функций в библиотеке может быть много, а переопределять имя каждой - не самое приятное занятие. По идее переопределение можно сделать с помощью специальной функции переименования. Где-нибудь в файле с определением зависимостей пользователь может задать функцию на вашем же языке, которая будет принимать и возвращать строку, и явно указывать, что данная функция должна переопределять имена для всех функций библиотеки X. Но это костыль по-моему.

Единый кодстайл принудительно

Как быть если совпадут имена результатов из разных функций? Нельзя их вызывать из одного места.
Или высокий уровень сложности: имя результата одной библиотеки совпадает с типом из другой.

А можно пойти ещё дальше и требовать точного совпадения ещё и аргументов функций, тогда не получится перепутать ширину и высоту. Правда, половина кода уйдет на переименование переменных и жонглирование областями видимости: const auto& arg = result; повсюду

и требовать точного совпадения ещё и аргументов функций

Это тоже планируется ввести

Правда, половина кода уйдет на переименование переменных и жонглирование областями видимости: const auto& arg = result; повсюду

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

Вот уж, придумать несуществующую проблему, а потом её криво "решить".

Если прям хочется, настройте LLM в code review для проверки семантики имён переменных

Проблема существует в legacy-проектах с разросшейся кодовой базой. LLM — хороший костыль, но они работают с уже существующим разнобоем. Предлагаемый подход — попытка предотвратить разнобой на системном уровне. Мое предложение — не замена код-ревью, а формализация соглашений. Так же как const формализует неизменяемость, а типы — структуру данных. Не для всех проектов, но для некоторых критически важных систем — возможно, оправданно.

legacy-проектам ваш язык никак не поможет. Да и проблемы таких проектов бесконечно далеки от неудачного выбора имён

legacy-проектам ваш язык никак не поможет. 

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

Да и проблемы таких проектов бесконечно далеки от неудачного выбора имён

Вы недооцениваете проблему, потому что рассматриваете её в парадигме «стиль кода». На деле это проблема семантической целостности распределённой кодовой базы, которая в legacy проявляется особенно остро. LLM — паллиатив, который работает с симптомами. Мой подход — попытка лечения причины через формализацию контрактов. Вы говорите: "Зачем строгие имена, если есть LLM?" это тоже самое что как говорить в 1995 году: "Зачем нам статическая типизация в Java, если есть хорошие тестировщики?"

Вы знаете, есть интересный парадокс в нашей индустрии. Те, кто начинал в 90-е (и я в этом уверен, у вас богатый опыт), выработали железобетонный рефлекс: "Если работает — не трогай". И именно поэтому вы так скептически смотрите на предложения "всё переделать по-новому". Потому что видели, как "новые правильные подходы" разбивались о реальность дедлайнов, ограниченной памяти и процессоров, которые сегодня кажутся игрушечными.

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

Вы смотрите на моё предложение и видите: "Ещё одна умная идея, которая разобьётся о реальные проекты". Потому что ваша реальность 20 лет назад — это C++ без STL, без RAII, где каждая лишняя проверка — это просадка производительности.

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

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

Так что желаю вам удачи и творческих успехов.

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

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

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

Жаль. Настоящая инженерия начинается там, где заканчивается догматизм. Удачи вам в рамках вашей картины мира.

  1. Различные сущности выделять в различные типы.

  2. Использовать аннотированные (именованные) параметры функций.

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

оверинж нейроманьяка, который не только сам не умеет писать нормальный код, так ещё и пытается усложнить жизнь другим.

Могли бы аргументировать выражение "сам не умеет писать нормальный код, так ещё и пытается усложнить жизнь другим"?

1) Начинать надо с правильных названий функций, это ещё важнее:
чтоб в названии функции был возвращаемый тип,
типа:
FindUser()
CreateUser()
FindUserAndGroup()
ещё лучше:
Find_User() с подчёркиванием т.к. таких Find итак уже сотни, но некоторые не рекоммендуют смешивать стили snake_case и CamelCase

2) когда решите вопрос (1) с правильными названиями функций,
только после этого можно переходить к правильным названиям переменных

Данные название были выбраны для пример :/. В данном случае можно бесконечно угодно спорить как правильно назвать. Ровным счётом я могу сказать что подходит названия get_db_player() и так далее. Это были примеры, а не финальная реализация. В таких случаях имена выбираются для наглядности, а не для соблюдения всех соглашений. Критиковать учебный или иллюстративный код за имена функций — странно, честно говоря. Либо тогда, возможно, стоит перечитать, что такое «пример», и зачем он вообще нужен. Мои навыки писать код проверяются не по именам в демонстрационном фрагменте.

Как мне кажется тут больше проблема в auto, с прямым указанием типа проблема просто исчезает

Useruser = get_user(user_id)

Useradmin = get_user(admin_id)

if(user=admin){

hack_pentagon()

}

Идея автора статьи хороша, а проблема актуальна. Требовать в линтере или CI/CD чтобы было user1 = get_user() - вполне разумно. В python/javascript мы легко это порешали регулярками по строке, в скрипте, запускающем автотесты. Дилемма snake/Camel оказалась первопричиной разнобоя. Фронты и бэки, после жуткого срача и попойки с дракой (да, у нас настоящий стартап) - провели компитишн. И оказалось что snake_case тупо проще и скорее в наборе на ~20%. На нем и остановились.

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

Публикации