Привет, пикабушники! 👋 Это не просто пост — это точка начала. Первый сезон был про знакомство с ИИ и учёбу. Второй — про реальный продукт.
❓ Зачем вообще свой проект?
Учебные задачи — это тренажёры. Ты решаешь, закрываешь и забываешь. А проект — это когда ты идёшь по болоту без карты. Или с картой, которую рисуешь на ходу.
Я уже делал сайт на Vue.js — простой, но работающий. Тогда у меня не было ИИ. Теперь у меня есть Логос, и я пересобрал его не как учителя, а как соавтора. Это уже не «вопрос-ответ», а диалог двух проектировщиков.
🎯 Цель: перенести настолку в онлайн
Да, ту самую, из коробки, которая пылится на полке, потому что друзья разбежались. Я скинул Логосу PDF с правилами. Он, как любой хороший ИИ, сразу выдал километровый код и архитектурные предложения. И я его остановил:
«Логос, стоп. Сначала — механики, потом код. Ты знаешь, какие карты есть в игре?» «Нет» «Тогда не лети вперёд паровоза»
Этот момент — ключевой. ИИ — это инструмент ускорения, а не замена мышлению.
📋 Первый реальный шаг: таблица карт
Я сел и вручную переписал все карты и правила в таблицу. Пара вечеров — и у нас с Логосом появилось общее понимание того, что мы строим. Без этого — никакой архитектуры. Проект начинается не с кода, а с данных.
💬 Вопросы к сообществу (не ради галочки)
Как вам вообще моя затея? Серьёзно. Мне нужен честный взгляд со стороны. Есть ли в этом смысл или я занимаюсь ерундой?
У кого был опыт, когда проект на старте казался простым, а потом превращался в снежный ком? Как вы не сдавались и доводили дело до конца?
Какую настолку вы бы сами перенесли в онлайн, если бы умели кодить? Может, я выбрал не ту игру? Посоветуйте — я открыт.
📌 Что дальше?
Следующий шаг — в следующем посте. 💯
P.S. Т-академия отказала. И это, наверное, к лучшему — теперь точно никто не будет отвлекать от моего собственного маршрута. Иногда «нет» — это «да» с отсрочкой.
А вы сталкивались с отказами, которые обернулись удачей? Расскажите — вдохновимся вместе.
Привет, пикабушники! 👋 Давно меня не было? Есть такое. Может, кто-то подумал, что я слился, забросил всё и ушёл в закат с мясным ножом в руке. А вот фигушки! 😄 Я всё это время учился. Реально, без шуток. И сегодня у меня есть чем похвастаться.
✅ ГЛАВНАЯ НОВОСТЬ: Я ЗАКОНЧИЛ JAVARUSH!
Да-да, тот самый курс, с которого я начал свой путь. Скриншот прилагаю — чтобы вы не думали, что я вру:
Это не первый мой курс по программированию, но первый, который я прошёл в тандеме с DeepSeek. И разница — просто небо и земля: - 🚀 Учился в разы быстрее — потому что Логос (мой AI-напарник) всегда был под рукой - 🧠 Любую непонятную тему можно было разжевать до состояния каши — пока не дойдёт - 💬 Прокачал навык общения с ИИ — теперь я не просто спрашиваю, а выстраиваю диалог , как с живым коллегой
🤔 А ЧТО ДАЛЬШЕ? ГЛАВНЫЙ ВОПРОС
Скажу честно: в текущей ситуации я мало верю, что меня возьмут джуном сходу. Поэтому активных поисков работы пока не веду. Но пару недель назад на почту пришла ссылка: Т-академия набирает учеников. Я заполнил анкету, прошёл тесты. Возьмут — не возьмут, пока хз. И если возьмут, пойду ли — тоже под вопросом. Но шаг сделан, это уже прогресс.
🎲 ПРОЕКТ, КОТОРЫЙ РОДИЛСЯ ИЗ БОЛИ
Все вокруг твердят: «Сделай свой проект! Вот тогда ты реально разработчик». А я сижу и думаю: «Что делать-то?» Всё уже придумали, всё уже есть. И тут ИИ выдал гениальную простоту: «Сделай то, чего тебе не хватает в жизни». А чего мне не хватает? Я люблю настольные игры. У меня даже небольшая коллекция. Но с годами играть стало не с кем: - У друзей нет времени - Кто-то уехал - Кому-то просто не интересно
И я понял: я же не один такой! В других городах и странах полно людей, которые хотят играть, но не с кем. И тут меня осенило. 💡 Я открыл коробку с одной из своих любимых игр и понял: я хочу перенести её онлайн. Посовещались с ИИ. Пришли к выводу: будет сложно, но шаг за шагом — реально.
🎬 СЕЗОН 2: ОТ ЗНАКОМСТВА К РЕАЛЬНОМУ ПРОЕКТУ Так что первый сезон нашего сериала «Мясник + ИИ = знакомство» я считаю завершённым. И объявляю о старте второго сезона: Butcher in IT: реальный проект 🚀 Я буду делать онлайн-версию настолки. С нуля. На Java. С помощью ИИ. И всё это — на ваших глазах.
💬 ВОПРОСЫ К СООБЩЕСТВУ (ОТ МЯСНИКА С АМБИЦИЯМИ)
1. Кто-нибудь сталкивался с переносом реальной настольной игры в онлайн? Это вообще легально? 🤔 2. Нужно ли писать издателю и просить разрешения? Или можно сделать «свою» версию с изменённой механикой? 3. Стоит ли менять правила, чтобы игра стала «уникальной», или сделать точную копию? 4. Если у кого-то есть опыт подобных проектов — расскажите, с чего начать, чтобы не утонуть в коде с первых дней? 🙏
---
P.S. Подписывайтесь, если ещё не — дальше будет жарко! 🔥 Я впервые за долгое время вижу конкретную цель, а не просто «хочу в IT».
P.P.S. И да, я всё ещё режу мясо. Но теперь ещё и пишу код. Две параллельные вселенные, одна голова. 😅
Если у тебя в середине нулевых был кнопочный телефон с Java, то ты уже был человеком не самым последним. А если на нём ещё шли нормальные игры, то на переменах вокруг тебя автоматически собирались люди, некоторые даже умудрялись обменивать время на чипсы, пиццу из столовой и прочие ништяки, а шкет из паралели перекидывал топовые игры со своего Сименса по 5 рублей за игру (Кто знает, тот знает, почему именно сименс)
Игры тогда скачивались с каких-нибудь Tegos, DimonVideo. Весили они смешно, но залипали в них часами.
Вот мой личный топ Java-игр, в которые я реально рубился на телефоне. я намеренно убрал Gravity defied, так как с этой игры начинается любой топ, ну или дудл джамп, т.к он уже появился в эпоху IOS и Android
Prince of Persia
Да, именно Java-версия, а не большая компьютерная.
Для кнопочного телефона это была вообще пушка. Бегаешь по дворцу, прыгаешь, карабкаешься, избегаешь ловушек, и всё это с ощущением, будто у тебя в руках почти настоящая консоль. После совсем простеньких мобилок эта игра казалась чем-то взрослым.
Особенно мне нравилось, что в ней был какой-то дух приключения. Не просто "нажимай кнопки и собирай монетки", а реально путь через ловушки, стены, шипы и всю эту восточную атмосферу. Для маленького экрана это работало очень даже бодро, да я бы и сейчас поиграл в неё на кнопочном телефоне, на каком нибудь Nokia 6233
Asphalt
Какая именно часть была у меня первой, я уже честно не помню. По-моему, я успел скачать несколько, и отличались они одна от другой не то чтобы сильно
Но суть была в другом. Это были гонки, а значит уже автоматом кайф. Машины, скорость, нитро, ночной город, повороты, в которые ты не входишь вообще никогда, потому что на кнопочном телефоне управлять тачкой это отдельный вид страдания, а некоторые игры серии вот как сейчас помню на эриксонах работали только от джойстика, которые у эриксонов и так дохлые были.
Splinter Cell
Вот эта игра мне тогда вообще снесла башку.
После обычных платформеров и гонок внезапно получить на телефоне что-то в духе скрытного боевика было очень круто. Ползёшь, прячешься, обходишь врагов, следишь за светом, стараешься не палиться. Для Java это ощущалось очень необычно.
Ну и сам образ челика с его зелёными очками уже тогда был ультра-крутым. Ты вроде сидишь с кнопочной мобилой за партой, или ночью на подушке, а в голове у тебя почти шпионский триллер, хоть я и не очень любил игры на ПК где нужно стелсить, эта игра была очень выдержана и динамична.
Age of Heroes
Вот это уже прям жир.
Одна из тех игр, в которых можно было конкретно засесть. Не на пять минут, а нормально. Фэнтези, герой, прокачка, сражения, локации, вся эта атмосфера приключения. Для телефона это казалось огромной игрой.
Мне всегда нравились такие штуки, где ты не просто проходишь уровень за уровнем, а именно идёшь по миру, лутаешь, дерёшься, качаешься и чувствуешь, что у тебя есть хоть какое-то развитие персонажа. Age of Heroes как раз давала это ощущение. и как классно что у телефонов была память и сохранения, например на приставке у меня был картридж без батарейки, и попробуйте Final Fantasy пройти за один присест, во первых долго, во вторых кинескоп посадишь.
Magnetic Joe
Вот тут, возможно, не все со мной согласятся, но лично мне Magnetic Joe нравилась больше, чем тот же Bounce. Хотя концептуально игры, конечно, разные.
В Magnetic Joe был свой очень кайфовый вайб. Маленький робот, магниты, платформы, вся эта механика с притягиванием и отталкиванием реально цепляла. Игра не выглядела слишком шумной или перегруженной, но в ней было какое-то особое удовольствие от самого процесса.
Мне она запомнилась именно как одна из самых приятных Java-игр. Не самая хайповая, не самая попсовая, но очень цепляющая. Бывают такие игры, которые не у всех на слуху, но лично тебе заходят вообще идеально. Вот Magnetic Joe у меня как раз из таких.
Послесловие:
Вообще забавно, насколько мощные эмоции тогда могли вызывать игры, которые сегодня весят меньше одной фотки в памяти телефона.
Но в этом и был весь кайф. Телефон кнопочный, экран маленький, памяти почти нет, а удовольствия было дофига, и как классно было когда родители покупают новую мобилу уже с поддержкой карт памяти, можно было не выбирать, а качать всё что угодно, пока трафик не кончится.
Да конечно, я не добавил в этот топ: Кто хочет стать миллионером, Gangster Crime city, Bounce, AssАсинс Крид, пишите в комментариях свой топ игр
Короче, я, похоже, иду по понижающей по поколениям, так что следующим постом сделаю уже мой личный топ игр на SEGA, а потом и на Dendy, только не заставляйте меня делать топ игр на Тетрисе.
playman extreme running уверен, что многие из вас играли в эту игру. Это было ещё на кнопочных телефонах, в те далёкие времена. К сожалению, я эту игру застал намного позже.
Игра и по сей день одна из лучших игр на Java платформе. И вчера я её наконец то прошёл. В игре нет ничего сложного, но над последними уровнями пришлось попотеть.
Знаю и видел, что есть очень крутые игроки в эту игру, которые играют очень красиво и делают много трюков. Конечно это далёко не единственная популярная игра на Jawa, но её узнаваемость огромная.
Сумели ли вы пройти все уровни ? Два самых сложных уровня в игре
На относительно простом примере показываю как можно сделать программу «снова великой».
*разумеется это нейросетевая Дженна а не реальная, которая про рефакторинг ничего не знает.
Исходный код отрефакторенной версии выложен на Github.
Задача
Допустим есть некий софт, созданный еще «при царе Горохе» неким гордым но умным одиночкой, которого с тех пор никто не видел. Софт живой и с пользователями, которые приносят прибыль, поэтому его надо как‑то развивать и поддерживать.
В попытке расширить команду разработки, вы начинаете нанимать новых разработчиков, но раз за разом происходит одна и та же ситуация:
проработав месяц-два, нанятые программисты в ужасе убегают в закат.
Кто‑то при увольнении намекает на причины такого поступка, в диапазоне от «ваш проект попахивает» до надо «срочно все переписать». После примерно десятого убежавшего программиста, вы наконец начинаете задумываться, что возможно с проектом действительно что‑то не так и стоит провести этот самый «рефакторинг».
Так это обычно начинается.
Образец
В качестве образца для этой статьи был взят один интересный но малоизвестный широкой публике проект JPC:
The fast x86 PC emulator in 100% pure Java
Самый настоящий эмулятор старого x86-компьютера, реализованный без всяких нативных частей — на чистой Java!
Не очень большой (~6500 файлов с исходным кодом), но имеет стадию «внутренней генерации» — часть исходного кода создана не вручную а путем запуска кодогенератора по метаданным, что довольно часто встречается у больших проектов с историей, причем на любом языке.
Еще к сожалению JPC немного заброшен, что для статьи только в плюс поскольку добавляет реалистичности — именно в таком состоянии чаще всего пребывают проекты, которые просят «привести в чувство».
Текущее состояние
После переезда проекта на Github, список коммитов выглядит следующим образом:
Мягко говоря негусто.
Никаких тестов в проекте нет, по всей видимости тестировалось все вручную с помощью молитвы, еще судя по исходному коду — далеко не весь функционал является рабочим, что также характерно для проектов в «пред‑рефакторинговом» состоянии.
Думаю теперь очевидно почему для этой статьи был выбран именно JPC — несмотря на редкость решаемой задачи, для рефакторинга это самый типичный клиент.
Собирается сей чудо-проект с помощью.. Makefile, что для мира Java является дичью и извращением редкостью:
make application
Примерно как собирать проект на QT с помощью Gradle или (еще лучше) — sbt.
Предложите как-нибудь коллегам и посмотрите на реакцию, некоторые точно перестанут с вами здороваться за руку.
Для сборки необходимо указать путь к JDK в переменной PATH, при этом сборка проходит успешно даже с последними версиями (автор использовал OpenJDK 21).
Готовое приложение JPCApplication.jar появится в корне проекта после завершения сборки, но на этом хорошие новости заканчиваются:
Собранное приложение отказывается запускаться, что мы исправим чуть ниже.
Стадия первая: новый скелет
Первым делом, как и в реальном боевом проекте, необходимо избавиться от любого «самопала», задействованного при сборке. Причина, почему этот шаг критически важен на самом деле не так очевидна:
статические анализаторы — главный иструмент рефакторинга, крепко привязаны к структуре проекта и стандартным средствам сборки
Разумеется существуют варианты и с произвольной структурой проекта, но эффективность рефакторинга будет заметно ниже. Поэтому автор сделал стандартный (для своей практики) «финт ушами»:
перевел сборку проекта на Apache Maven, максимально широко поддерживаемый средствами анализа кода, CI-системами и средами разработки.
Реализовать такую миграцию в данном случае оказалось очень просто, поскольку JPC совсем не использует внешние библиотеки. Все что я сделал — раскидал ресурсы и исходный код в стандартную для Maven структуру каталогов:
Исходный код был перенесен из каталога src в src/main/java, ресурсы — в src/main/resources. Также был добавлен очень простой pom.xml, описывающий минимальные шаги сборки проекта:
Это было убрано, поскольку точно такой же параметр запуска зашит еще и в код:
Наследие «былых времен», которое также достаточно часто встречается в устаревших проектах — во времена Java 1.5 и апплетов было модным использовать собственные атрибуты в манифесте.
Стадия вторая: удаление ненужного
Как в практически любом долгоживущем проекте, в JPC есть свои «внутренние утилиты» — отдельные программы, написанные для задач внутренней автоматизации.
Это та самая «грязная рабочая поверхность», которую не видит конечный пользователь.
При проведении рефакторинга, трогать внутренние утилиты стоит в последнюю очередь и в самом крайнем случае, поскольку правильность их работы проверять тяжело (ни тестов ни документации для внутренних утилит обычно нет в природе), зато они сильно влияют на общую работоспособность проекта.
В JPC внутренние утилиты реализованы в виде отдельных классов в пакете «tools» и нескольких шелл-скриптов в корне проекта.
И то и другое я просто не стал переносить в новую версию, также я убрал часть исходного кода эмулятора, отвечающего за отладку (пакет org.jpc.debugger) — по той же самой причине.
был убран импорт класса org.jpc.debugger.LinearMemoryViewer а используемая статичная функция translateLinearAddressToInt перенесена в класс PC.
Все эти действия позволили сократить кодовую базу проекта практически вдвое, что сильно упростило следующий шаг рефакторинга.
Стадия третья: критические проблемы
Наконец мы подошли непосредственно к самому рефакторингу, который я буду проводить с помощью среды разработки Intellij Idea.
Первый запуск анализатора дает следующий результат:
24 критических ошибки и ~ 37 тысяч предупреждений — не так уж плохо, по сравнению с тем что бывает на свете.
Смотрим глубже и видим, что все 24 ошибки — действительно самые критичные, поскольку из-за них проект может перестать собираться в самом ближайшем будущем:
Так что эти места стоит рефакторить в первую очередь, пока проект хотя-бы собирается из исходников.
Есть и хорошая новость:
Как видно из скриншота выше, большая часть критичных ошибок гнездится в классе JPCApplet, который используется для запуска приложения в режиме Java-апплета — ныне устаревшей технологии, когда-то работавшей с помощью плагина для браузера.
Поскольку плагин более официально не поддерживается — вся технология приказала долго жить и у обычных пользователей не встречается, так что класс можно удалить.
Но все несколько сложнее, поскольку еще есть вложенные классы, один из которых используется снаружи (org.jpc.j2se.JPCApplication):
JPCApplet.PlayPausePanel pp = new JPCApplet.PlayPausePanel(this);
Я просто перенес этот класс по месту использования, что позволило наконец удалить JPCApplet из проекта целиком.
Получилось минус 13 критических ошибок.
Еще один источник проблем — класс LinkBorder также можно удалить, поскольку он использовался лишь из удаленного JPCApplet.
Что дало еще минус три критических ошибки.
Дальше смотрим класс org.jpc.emulator.peripheral.Mixer, который забит предупреждениями от анализатора буквально через каждую строчку, однако вносить массовые правки пока не стоит — «всемогущая» Idea временами ошибается и это именно такой случай.
Анализатор ругается (в первую очередь) на конструктор new Float(), поскольку его прямое использование объявлено устаревшим, а в новых версиях Java стоит использовать Float.valueOf() в качестве замены.
Но как только вы замените конструктор, анализатор подскажет еще несколько оптимизаций, так что конечный вариант будет достаточно сильно отличаться:
Ругается анализатор на уникальный метод stop(), который был отмечен как устаревший еще до того как я начал писать на Java:
'stop()' is deprecated since version 1.2 and marked for removal
Примерно до версии 1.8 использование данного метода еще можно было как‑то оправдать наличием устаревших библиотек, в нынешних реалиях этот метод — просто еще один способ «выстрелить себе в ногу»:
Stopping a thread causes it to unlock all the monitors that it has locked.
Так что в коде использование этого метода точно стоит заменить на стандартный .interrupt() :
if (runner.isAlive()) { runner.interrupt(); }
Блок try-catch также можно спокойно убрать, поскольку SecurityException не выбрасывается в новых версиях Java при попытке остановки нити.
На этом все критические проблемы в проекте решены и получен минимальный практический смысл от всей затеи:
убраны места, которые могут сломать сборку проекта в новых версиях Java
Стадия четвертая: ошибки выполнения
Пришло время наконец попробовать запустить нашего «франкенштейна».
Сборка разумеется завершится успешно (не зря же старались), но при запуске будет выбрасываться все та же ошибка поиска ресурсов:
В оригинальной версии JPC, часть ресурсов (например образы биоса) загружались только из jar‑файла, часть (образы дисков) — только снаружи, из каталога resources, при этом каталог с ресурсами был общим.
Сию дичь необходимо пресечь и сделать в более адекватном стиле, например как это реализовано в движке знаменитого Quake:
сначала ищем внешний файл, если не найден — ищем в ресурсах, если не найден в ресурсах — падаем с ошибкой
За чтение образа BIOS отвечает вот такой метод в классе org.jpc.emulator.motherboard.Bios:
private staticfinalbyte[] getBiosData(String image) throws IOException { InputStream in = Bios.class.getResourceAsStream(image); if (in == null) { thrownew IOException("resource not found: " + image); } try { ByteArrayOutputStream bout = new ByteArrayOutputStream();
while (true) { int ch = in.read(); if (ch < 0) { break; } bout.write((byte) ch); }
В принципе за такую реализацию уже можно начинать бить, спасает лишь факт, что столь идиотсткое побайтовое чтение работает исключительно с ресурсами, которые уже находятся в памяти.
File f = new File(image); if (f.exists() && f.isFile() && f.canRead()) return Files.readAllBytes(f.toPath());
f = new File("resources",image); if (f.exists() && f.isFile() && f.canRead()) return Files.readAllBytes(f.toPath());
final URL u = Bios.class.getResource(image); if (u == null) thrownew IOException("resource (bios) not found: %s".formatted(image));
try (InputStream in = u.openStream()) { return in.readAllBytes(); } }
Логика переделана на возможности современной Java 17, поэтому кода стало сильно меньше, также были добавлены проверки на наличие ресурса:
по полному пути,
по частичному (предполагается что файл находится в каталоге resources),
поиск внутри jar приложения.
Но при следующей попытке запуска получаем еще одно исключение, уже в другом месте:
Причиной является искусственная проверка:
if (!(cl instanceof URLClassLoader)) thrownew IllegalStateException();
Когда-то давно системный загрузчик классов действительно наследовался от URLClassLoader, так что проверка бы отработала.
Несмотря на то, что подобные искусственные проверки служат вообщем‑то хорошей цели раннего обнаружения проблем, временами разработчики перебарщивают и пытаются контролировать то что контролю не поддается.
Однако одним лишь удалением проверки дело не ограничилось — необходимо почистить еще один метод, реализующий «закат солнца вручную»:
if (!dir.equals(thisDir)) continue; resources.add(name); }
jarStream.close(); } catch (IOException e) { e.printStackTrace();} } InputStream stream = context.getResourceAsStream(directory); try { if (stream != null) { Reader r = new InputStreamReader(stream); StringBuilder sb = newStringBuilder(); char[] buffer = newchar[1024]; try { while (true) { int length = r.read(buffer); if (length < 0) { break; } sb.append(buffer, 0, length); } } finally { r.close(); }
for (String s : sb.toString().split("\n")) { if (context.getResource(directory + s) != null) { resources.add(s); } } } } catch (IOException e) { LOGGING.log(Level.INFO, "Exception reading images directory stream", e); }
return resources.iterator(); }
Тут происходит поиск доступных образов дисков путем последовательного перебора всех файлов внутри .jar с приложением.
С учетом того что .class файлов внутри ~6500 — такое решение мягко говоря «не оптимально».
Вообще говоря любой поиск ресурсов через перебор во время работы приложения является медленным, это и есть основная причина медленного запуска любого приложения на (например) Spring Boot.
Поскольку в проекте используется очень небольшое количество образов диска и нет вариантов по резкому увеличению их количества, я просто зашил названия в код:
privatestatic Iterator<String> getResources(String directory) { final List<String> resources = new ArrayList<String> (Arrays.stream(IMAGES).toList()); final File f = new File(directory);
if (!f.exists() || !f.isDirectory()) { return resources.iterator(); }
final File[] files = f.listFiles();
if (files == null) { return resources.iterator(); } for (File ff: files) { resources.add(directory + ff.getName()); }
return resources.iterator(); }
Метод getResources() используется для отображения списка доступных образов дисков через меню приложения, все внутренние образы (зашитые в.jar) добавляются в этот список автоматически.
После столь примитивной правки, приложение стало запускаться визуально быстрее даже на мощном современном ноутбуке, так что не стоит недооценивать силу простых решений ;)
Хотя всех правок выше оказалось недостаточно, следующая остановка — класс org.jpc.support.ArrayBackedSeekableIODevice, который (внезапно) играет ключевую роль в проекте.
Метод configure() отвечает непосредственно за загрузку образов дисков и дискет:
File f = new File(spec); if (f.exists() && f.isFile() && f.canRead()) { imageData = Files.readAllBytes(f.toPath()); length = imageData.length; return; }
f = new File("resources",spec); if (f.exists() && f.isFile() && f.canRead()) { imageData = Files.readAllBytes(f.toPath()); length = imageData.length; return; } final URL u = ArrayBackedSeekableIODevice.class.getResource(spec); if (u == null) thrownew IOException("resource (image) not found: %s" .formatted(spec));
На этой стадии была проведена самая настоящая «коммерческая оптимизация» — доведен до ума функционал актуальный конечным пользователям.
Это уже не стандартные сказки про «технический долг» и «плохую архитектуру», а вполне себе осязаемый результат, который можно потрогать.
Так что вас за такое-то скотство, проведенное с рабочим проектом уже точно не уволят ;)
Стадия пятая: массовые правки
Все описанное выше — обязательные базовые части, без которых рефакторинг вообще не может состояться как согласованный с бизнесом и оплаченный процесс. Но можно зайти дальше — в действительно рисковую зону, где ваши действия могут иметь не всегда предсказуемые последствия:
массовые и сквозные правки исходного кода, во всем проекте целиком
Рабочая область выглядит как-то так:
Собственно все «желтенькое» на скриншоте ниже — места для рефакторинга, заботливо подсказанные средой разработки:
К сожалению на практике все несколько сложнее чем подсказывает Idea и просто нажимать «Alt + Shift + Enter» на каждую подсказку не стоит:
Все потому, что в проекте активно используется Reflection API для загрузки и обращения к методам класса необычными способами:
Что сводит анализаторы исходного кода с ума, поэтому примерно половина методов в проекте десктоп-приложения, не использующего никакие IoC-контейнеры отмечено как неиспользуемые:
Скотство?
Конечно скотство, но и в реальных больших и старых проектах такое тоже будет в обязательном порядке — когда‑то использование Reflection API считалось модным и молодежным явлением, убрать которое "под капот" смогли только те самые IoC‑контейнеры вроде Spring.
Следующим примером кода, нуждающегося в массовой зачистке является использование анонимных классов:
Лямбды появились еще в Java 8 и с тех пор уже нет никакого здравого смысла их игнорировать — они здорово сокращают объем кода:
В любом legacy-проекте, особенно если это приложение для десктопа такого будет очень и очень много:
Следующий повод для массовых правок — прямой результат ручной разработки, без использования средств проверки и анализа кода:
Хороший пример, подсказанный анализатором:
Разумеется это не является критической проблемой, поскольку эти модификаторы ничего не делают, но таких мест очень много и в сумме они дают ненужное увеличение объема кодовой базы.
Следующие две проблемы — также частые гости устаревших проектов:
Точно также как и с ненужными модификаторами в интерфейсе, всего лишь занимают место и увеличивают объем кода.
Хотя пример выше это совсем уж старый код, поскольку метод Arrays.asList () появился еще в Java 7 — былинные времена далекого прошлого, как можно было его сохранить до сих пор — загадка.
Эпилог
Если вы никогда не видели JPC то стоит посмотреть, поскольку он в свое время несколько расширил мнение о возможности Java, в первую очередь в плане производительности — тема о которой много и сильно шутили еще 10 лет назад.
Ну а если перед вами стоит задача провести подобный рефакторинг — стадии с первой по четвертую фактически являются руководством к действию.
Массовые правки я бы с ходу делать не рекомендовал — очень уж высокие риски, что что‑то пойдет не так.
Еще в реальном проекте процесс рефакторинга скорее всего сильно затянется, поэтому вам придется делать промежуточные срезы и синхронизировать ваш рефакторинг с обычной разработкой, о чем стоит помнить до начала всего действа.
Рассказываю как мы сделали самые крутые визитки на Диком Западе в отечественной ИТ-индустрии.
Внимание на код - он полностью рабочий!
Все началось когда автор наткнулся на одну интересную статью где эксперт по 3D-технологиям вместил специально оптимизированный и обфусцированный код рейтрейсера на C++ в размеры своей визитки.
Мы позеленели от зависти тоже захотели себе что-то такое, но поскольку занимаемся все же серверами а не 3D-графикой и больше Java, чем C++ — решили что будет круто уместить на обратной стороне нашей визитки простейший HTTP-сервер на Java.
Вместе с запуском и компиляцией.
Еще при наличии графического окружения будет запущен браузер.
Плюс немного криптографии для защиты от подделки.
Весь код уместился в 18 строк, выровненных по ширине так чтобы влезть в размеры визитки:
Вбиваете код с визитки в любимый редактор, сохраняете файл как vcard.sh и запускаете:
Локально запустится простейший HTTP-сервер, который отдаст текстовую страничку с нашими контактами. При наличии GUI — запустится еще и браузер по-умолчанию, с автоматическим открытием страницы этого сервера.
И все это в 18 строк кода.
Да, еще будет нужен любой Linux/BSD/MacOS/Solaris и любая версия JDK начиная с 1.8 на машине.
Поддержку запуска на Windows делать не стал (хотя это и возможно технически), но можно спокойно запустить в WSL .
Чтобы вы не мучились с вводом кода с картинки, вот текстовая версия:
Тут используется связка из заголовочного shell-скрипта и слегка обфусцированного кода на Java. Еще я не стал кодировать весь блок на Java полностью в HEX-строку, чтобы визуально оставалось ощущение исходного кода.
Это shebang, стандартное для Unix указание на используемый интерпретатор, про него и так все знают. Дальше происходит создание временного каталога в /tmp и присваивание его имени переменной в скрипте:
t=$(mktemp -d);
Затем получение скриптом собственного имени с полным путем:
e=$(realpath $0);
Чтение скриптом самого себя, с отрезанием первых 4х строк - чтобы получить блок кода на Java:
sed '1,4d' $0
Дальше начинается pipe, в котором результат предыдущей команды передается на вход следующей:
Результат всех преобразований записывается в файл Yo.java, в том самом временном каталоге.
Малоизвестная опция -XDignore.symbol.file отключает предупреждение об использовании системных классов JDK (com.sun.net.httpserver.*) в проекте — в 1.8 версии классы встроенного в JDK HTTP-сервера еще считались системными.
Запуск с передачей полного пути оригинального скрипта для последующего его чтения из Java-кода:
java -cp . Yo $e
Сам код после деобфускации и форматирования выглядит уже вот так:
Тут уже большая часть логики вполне очевидна, поэтому раскрою лишь два самых сложных фрагмента.
Криптография
Когда я только начинал думать над реализацией этой штуки, уже было ясно что нужен какой-то неочевидный контроль целостности:
исходный код очевидно будут пересылать через сообщения, в виде постов или по почте, что легко его сломает.
Поэтому хотелось хоть какую-то защиту от подделки содержимого, чтобы компьютерные дети не добавили патч Брамина в самое интересное место, а индийский паренек не подменил авторство и мои контакты на свои, ради строчки в резюме.
Задачу усложнял факт передачи открытых исходников и ограничение по размерам, но видимо получилось:
static String ED = "1AtzGU0uq7J7DHPdjdJJ5JJDiwQi8mElIDOjuRK0DEU=";
На каждую попытку как-то подменить содержимое (включая заголовок) будет выдаваться вот такая ошибка:
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:981) at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1062) at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853) at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202) at Yo.main(Yo.java:6)
Получается код сам себя защищает от подделки.
0x7f000001
Вторым неочевидным моментом является вот такой странный адрес хоста:
String h = "0x7f000001";
Который используется при формировании ссылки для открытия браузером:
Desktop.getDesktop().browse(new URI("http://" + h + ":" + p));
Такое применение однозначно говорит о том что адрес очень даже стандартный, поскольку проходит как стадию валидации на стороне Java при формировании объекта URI, так и валидацию на стороне запускаемого браузера.
Это просто нотация, вариант написания IP-адреса 127.0.0.1, обозначающего loopback (петлю) — внутренний интерфейс, к которому можно подключиться локально, а не из сети.
Вот тут больше примеров различных вариантов написания IP-адресов, уверен — удивит даже бывалых админов.
P.S.
Статья была опубликована на Хабре, более фривольный оригинал статьи находится в нашем блоге, где мы подробно рассказываем об ужасах разработки, вгоняя в краску даже опытных и бывалых.
Народ, привет. Прошлый мой пост справедливо утопили в минусах - я выложил скрин чата но не показал сообщения за что по делу получил по шапке. Исправляюсь.
Я инди-разработчик, пишу мобильное приложение Vext в соло в Android Studio. Идея простая: мне до смерти надоело искать адекватных тиммейтов в дискорд-каналах, где сообщения улетают в космос со скоростью света. Захотелось сделать место, где ты заходишь, видишь профиль человека, его реальные теги игр и можете сразу списаться/собрать катку.
За пару месяцев в одиночку с Firebase я собрал вот это (все скрины реальные, с моего телефона):
Лента постов : Сделал полноценную ленту. Можно делиться своими камбеками (как мой вчерашний пот в CS 13-11), крепить медиа, ставить хэштеги. Долго воевал со скруглениями картинок, чтобы они не вылезали за края карточек, но вроде победил.
Экран Сообщество : Сюда вывел поиск игроков и список друзей. Отсюда можно быстро прыгнуть в диалог.
Чат : Живой чат с поддержкой отправки картинок и скриншотов. Сейчас как раз допиливаю верстку сообщений, чтобы они сжимались красиво, как в Телеграмме, и не ломали экран.
Профиль игрока : Самая важная часть. Тут выводятся кастомные теги (поддерживаешь Rust, CS или GTA - сразу видно). Планирую в будущем прикрутить сюда подтягивание реальной статистики из игр через API, чтобы сразу видеть KDA и ранг человека, а не верить на слово.
Зачем пишу?
Одному кодить в пустую консоль - это прямой путь к выгоранию, руки начинают опускаться. Приложение уже на стадии живой альфы (APK собирается, база работает). Мне безумно нужны 5-10 человек, которые любят игры, готовы потыкать приложение, сломать мне верстку на своих смартфонах и честно, сказать: удобно получилось или плохо, и что нужно добавить в первую очередь.
Дневник разработки и ссылку на альфу оставил ниже. Хейтите, критикуйте, советуйте — сейчас готов к любой живой критике.