Не смог пройти мимо, к сожалению для моего личного времени.
Что меня тут задержало? Вилка 160к, хвалебные отзывы, решаю углубится в твою профессиональную деятельность.
Если кому то интересно просто моё мнение и выводы на основе этого "Углубления", либо если кому то просто не интересны технические занудства, рекомендую сразу перейти к главе III.
Глава I - Portfolio ( YouTube )
Решаю поверхностно изучить работы автора, захожу на его первое актуальное видео,
система автобусника. Достаточно примитивная система в качестве демонстрационной, но все же она одна из самых свежих и по этому показательных. Изучив видео в глаза сразу бросаются маркеры установленные на +1 по Z от земли, что как бы уже намекает на уровень реализации, далее видим как автобус подъезжает к первой остановке и наблюдаем самый спорный момент реализации системы разработчиком за 160.000 рублей в месяц, поведение NPC. Все дёргано, криво, если NPC не успевает дойти, его телепортирует в транспорт. Слишком очевидная последовательность действий, там просто поочередно вызываются таски у NPC, что выглядит очень топорно, причем даже такая реализация в лоб все равно каким то образом допускает то что NPC толкают друг друга. Сразу префаером предполагаю что никакой синхронизации нормальной там нет ( почему я так решил, узнаем в главе II ).
Изучил ещё пару видео, ничего интересного, или хотя бы сложного я там не увидел, все на уровне задач начинающего разработчика, по этому сразу перейдем к более интересной части этого разбора.
Глава II - GitHub.
Так как я сам являюсь разработчиком, изучение гита коллег по цеху для меня особый вид удовольствия, всегда можно либо узнать для себя что то новое, либо, как в этом случае, увидеть своё прошлое, тот путь и код который остался далеко позади на пути саморазвития.
Ремарка. Я понимаю что система была написана 4 года назад, но автор 10 месяцев назад её актуализировал, по этому будем считать её свежей, даже если автор так не считает.
Так же далее будем смотреть динамику на более актуальных репах.
Демонстрация этой системы была также на YouTube по этому начнём с неё. Задумка системы простая, обеспечить возможность гибкой настройки урона оружия, переопределяя стандартный урон. Для реализации такой системы нужно обеспечить всего три основные вещи, возможность гибкой настройки, самое переопределение урона, а так же синхронизацию этого всего дела сервером для безопасности, звучит просто, разберём что мы увидели.
К сожалению автор по всей видимости на момент написания системы ещё не был знаком с TypeScript ( или не захотел внедрять в такую простую систему ), изучив остальные репозитория я не увидел примеров систем с TypeScript в целом, по этому далее на это внимания обращать не будем.
Первое что попалось в глаза, файл конфигурации системы. Файл вида JS object в котором присутствует несколько уровней вложенности, что уже как бы не совсем удобно для его конфигурации, но пик неудобства тут это то что ключ конфигурируемого оружия является хэшем оружия, а не его названием по типу "Pistol", думаю объяснять почему это очень неудобно объяснять не нужно, автору советую изучить тему маппинга для таких случаев. Разделение на конкретный хэш оружия и на группу оружия выглядит нормальным решением.
Идём в основной файл системы. defaultPercent - почему-то находится не в конфигурационном файле, что выглядит странно при его наличии.
Вся система находится в одном файле, что очень неудобно при масштабировании, автору советую делить код как минимум на два слоя, контроллер и бизнес логика, причём бизнес логику при необходимости делить так же на модули.
В методе encode мы видим throw new Error, который в дальнейшем при вызове этого метода нигде не обрабатывается, что является критической ошибкой при разработке любого уровня сложности системы, необработанные ошибки всего сложно читаемы, автору же советую использовать либо try catch при вызове методов которые могут дрогнуть ошибку, либо использовать mp.console.logError, что в этом кейсе более логично.
Очень много "magic numbers" в коде, по типу bondeIndex головы.
Почему то урон в голову у нас делится на 10, хотя в комментариях к коду автор вроде как хотел увеличить урон в голову, не совсем понятно.
Но это все мелочи, основной вопрос. Где синхронизация урона с сервером? Почему автор доверяет клиенту и предопределяет урон только на стороне клиента? По хорошему на клиенте мы должны были собирать информацию о выстреле на клиенте, после чего отправить эти данные на сервер для валидации этого урона и уже после этого отправлять на клиент новое состояние игрока ( HP ). Критический момент реализации.
rage-driftmode-freeroam / custom radio commit ( 10-5m ago )
Идём далее, к более интересному и свежему. У автора есть игровой мод с открытым исходным кодом, немного изучив репозиторий первое что кинулось в глаза - бинарники в гите, зачем? Непонятно. Никогда так не делайте, добавляйте бинарники в gitignore, если же там нужно специфическая версия билда либо указывайте её версию, либо заливайте билд отдельно.
Сосредоточимся на самом свежем что автор добавил в мод, систему костюмного радио, разберём сначала серверную часть.
Использование var считается устаревшей практикой, крайне рекомендую использовать let. Хотя далее автор все же использует let, не совсем понятно зачем использовать var в таком случае, если автор в курсе что существует let/const.
Теперь самое критичное, производительность кода и сама бизнес логика, это полный пиздец мягко говоря. Во первых - это совсем не читабельно и не масштабируемо, процедурный код без намёка на модульность, очень плохо. о производительности все ещё хуже, объясню кратко как работает его система, по сути есть какие то звуки которые должны проигрываться для машин, код написан так что каждые 50 ms ( 20~ раз в секунду ) код проходится по всем машинам на сервере, затем для каждого игрока на сервере идёт проверка находится ли он в слушателях, после чего игрок может добавится в слушатели, скорость выполнения O(N * M) где N машины, M игроки, это ОЧЕНЬ непроизводительный код, не продакшн уровня. Причём внутри самих циклов используются медленные indexOf, includes. Так же при отключении игрока он не удаляется со слушателей. Ещё прикол, итерация идёт по всем машинам, даже если звук есть у 1-2 машин, хранится serverSounds, но итерируемся мы по всем машинам, архитектурный абсурд. Я очень многое опускаю, выделяю основные моменты.
JavaScript:
var serverSounds = {};
// every 50ms update volume for all players in veh range
setInterval(function () {
setVehPosition();
}, 50);
function setVehPosition() {
mp.vehicles.forEach((veh) => {
if (!(veh.id in serverSounds) || serverSounds[veh.id].paused) return;
let maxRange = serverSounds[veh.id].range;
serverSounds[veh.id].listeners.forEach(player => {
let dist = veh.dist(player.position);
if (dist > maxRange) {
let idx = serverSounds[veh.id].listeners.indexOf(player);
serverSounds[veh.id].listeners.splice(idx, 1);
player.call('destroySound', [veh.id]);
} else {
let volume = serverSounds[veh.id].volume * (1 - (dist / maxRange));
volume = volume < 0 ? 0 : volume;
player.call('setSoundVolume', [veh.id, volume * volume]);
}
});
mp.players.forEachInRange(veh.position, maxRange, (player) => {
if (serverSounds[veh.id].listeners.includes(player)) return;
let dist = veh.dist(player.position);
let volume = serverSounds[veh.id].volume * (1 - (dist / maxRange));
volume = volume < 0 ? 0 : volume;
serverSounds[veh.id].listeners.push(player);
player.call(
'createSound',
[JSON.stringify(serverSounds[veh.id]), volume * volume]
);
});
if (serverSounds[veh.id].trackLength <= serverSounds[veh.id].startTime) {
serverSounds[id].listeners.forEach(player => {
player.call('destroySound', [id]);
});
delete serverSounds[id];
return;
}
serverSounds[veh.id].startTime += 50;
});
}
Ладно, идём дальше. Клиентская часть, тут вообще ад, код читать почти невозможно но я постарался.
Про полное отсутвие обработки ошибок и валидации уже говорить не нужно, это у автора в каждой системе.
Радиостанции объявлены ТРИ РАЗА в разных местах:
- radioStation. js (строки 6-61)
- cef/music_browser/index.html (строки 14-135)
- Вероятно где-то на серверной стороне
Небезопасные HTTP потоки, автор явно не думает о безопасности своего проекта.
"
http://ep128.hostingradio.ru: 8030/ep128",
"
http://80.82.43.248:8000/shanson",
Безумные математические операции, посмотрите и попробуйте понять что тут происходит, такое даже бесплатный ChatGPT не выдаст.
JavaScript:
// index.js строки 24-33
let s = cam_vector.x*(car_pos.y-cam_pos.y) - cam_vector.y*(car_pos.x-cam_pos. x),
a = 1;
if(s > 0) a = -1
else if(s < 0) a = 1
else a = 0;
let pan = Math.sqrt(1-(dx / dy).toFixed(3)*(dx / dy).toFixed(3))*a;
Тут явно видно что автор не знает о существовании промисов, за 160 тысяч как будто нужно было бы изучить асинхронное программирование в JS.
JavaScript:
// radioController.js - строки 48-56
for (let i = 0; i < 100 && radioStationName != stationName; i++) {
mp.game.audio.setRadioToStationName(stationName);
await mp.game.waitAsync(1);
}
Я бы мог ещё долго продолжать, но тут попросту нечего разбирать, общий уровень автора как разработчика был мне понятен ещё на северной части, тут я просто решил показать забавные моменты, указывающие на реальный уровень разработчика. Идём к заключению.
Глаза III - Заключение.
Если кто-то доверился мне и решил прочитать сразу заключение, вы ничего не потеряли и сэкономили своё время.
Потратив где-то час я могу прийти к выводу что данный разработчик находится на уровне Junior, не понимает основ и принципов программирования, абсолютно не разбирается в архитектуре backend приложений, ничего не слышал о high-load и оптимизации, и за 5 лет своей профессиональной/любительской деятельности не развился от слова совсем.
Крайне не рекомендую доверять ему сложную работу, все что вы получите в итоге - код который: невозможно масштабировать, неработоспособный в продакшене, нечитабельный и просто плохой. Так же ясное дело его резюме в данной теме абсолютно не соответствует реальности.
Автору же рекомендую не стоять на месте, более глубокого изучать программирование, больше практиковаться и нормально отнестись к критике.