Ошибки начинающих разработчиков майнкрафт плагинов и как их не допускать

Руководство Ошибки начинающих разработчиков майнкрафт плагинов и как их не допускать

Поддерживаемые версии
  1. Отсутствует
Рад вновь сесть за микрофон.

Я считаю начать стоит как обычно с того, что меня побудило к написанию данной статьи.
На данный момент я вижу просто небывалый наплыв разработчиков, которые выкладывают десятки своих новых плагинов сюда, на спиготмс.ру, будь то за тем, чтобы предложить свой лучший аналог уже имеющимся, но не достаточно хорошим по их мнению плагинам, либо же, чтобы просто выложить достаточно ресурсов, дабы затем опубликовать что-то более стоящее (и даже возможно оригинальное!) за деньги, но это не играет роли.
А роль тут играет то, что большинство из таковых плагинов, кои штампуются начиняющими кхэм... кодерами (прости господи) содержат в себе множество проблем. Будь то как незначительные, по типу упускания из вида возможности игнорировать отменённые ивенты, что иногда может привести к некоторым проблемам взаимодействия с другими плагинами, так и более серьёзные, как неправильное обращение с шедулерами, кешем и т.п., что может приводить к перегрузке процессора, либо излишнему потреблению памяти (ну или утечкам, плавали, значем).

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


⦁ Пункт 1 - Некорректная работа с конфигурацией.

Это наверное является основной ошибкой начинающих разработчиков в принципе.

В этом пункте можно выделить 2 побочных подпункта.

А) Получение значений конфигурации в рантайме

Ситуация:
Представим, что нам необходимо сделать так, чтобы игроку выводилось определённое сообщение из конфига раз в 10 секунд.

Пример плохого кода:
Java:
Bukkit.getScheduller().runTaskTimer(plugin, () -> {
    Player player = Bukkit.getPlayer(nickname);
    player.sendMessage(plugin.getConfig().getString("message"));
}, 200L, 200L);

Почему это плохо?
По тому, что каждый раз, когда мы обращаемся к конфигу таким образом - он получает данные из LinkedHashMap (смотрите org.bukkit.configuration.MemorySection), что накладывает дополнительный процент нагрузки на сервер.
ВАЖНО СКАЗАТЬ, что даже если нагрузка от этого может казаться (хотя фактически она и есть) незначительной и менять код нет необходимости - это является крайне неоправданной затратой ресурсов, а также в случае, если вашему плагину будет необходимо получать значения из конфига более часто - это уже может вылиться из незначительной в весьма большую нагрузку.

Решение:
Вместо того, чтобы постоянно обращаться к конфигу и получать оттуда значения - мы можем записать это значение заранее и после использовать его в дальнейшем.
Java:
String message = plugin.getConfig().getString("message");
Bukkit.getScheduller().runTaskTimer(plugin, () -> {
    Player player = Bukkit.getPlayer(nickname);
    player.sendMessage(message);
}, 200L, 200L);

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

Пример до:
Java:
// Сетап
public String usage, nopermission, cooldown;
public void setupConfigValues(FileConfiguration config) {
        usage = config.getString("usage"));
        nopermission = Util.colorize(config.getString("nopermission"));
        cooldown = Util.colorize(config.getString("cooldown"));
    }
// много кода спустя
player.sendMessage(usage);

С использованием record:
Java:
public ConfigValues configValues;
public void setupConfigValues(FileConfiguration config) {
        configValues = new ConfigValues(
                Util.colorize(config.getString("usage")),
                Util.colorize(config.getString("nopermission")),
                Util.colorize(config.getString("cooldown")),
        );
    }

public record ConfigValues(
          String usage,
          String nopermission,
          String cooldown
    ) {}

// много кода спустя
player.sendMessage(configValues.usage())

Думаю я дал достаточно информации

Б) Неэффективное получение данных из конфига.

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

Ситуация:
У нас есть следующая конфигурация:
YAML:
section:
  value-one: "valueone"
  value-two: "valuetwo"
  value-three: "valuethree"
И нам необходимо взять оттуда значения

Пример плохого кода:
Java:
String one = config.getString("section.value-one");
String two = config.getString("section.value-two");
String three = config.getString("section.value-three");

Почему это плохо?
Ответ прост. Поскольку мы уже знаем, что при получении данных из конфига таким образом у нас идёт их получение из хешмапы - нам стоит посмотреть ещё на один аспект. Сначала этот метод получит секцию конфигурации 'section' и только потом получит значение оттуда. Таким образом каждый раз хеш высчитывается дважды, так ещё и итерация к этому прибавляется.
Итого суммарно 6 раз мы вычисляем хеш, что, согласитесь, не очень, особенно если у вас большой конфиг со множеством значений, где кол-во вычислений может быть больше в разы.

Решение:
Вместо того, чтобы получать необходимые нам значения из конфига вышеописанным образом - мы сначала будем получать секцию конфигурации, откуда уже будем брать наши значения.
Java:
ConfigurationSection section = configuration.getConfigurationSection("section");
String one = section.getString("value-one");
String two = section.getString("value-two");
String two = section.getString("value-three");
Этот подход будет в разы эффективнее, чем предыдущий.


⦁ Пункт 2 - Страх перед локальными переменными/константами и прочем.

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

И в целом назначения у них обоих относительно понятные - читаемость, удобство, НО, есть и у них такая черта - ваш код может стать производительнее за счёт их использования.

Ситуация 1:
Нам необходимо сделать метод для колоризации сообщений в hex формате.

Пример плохого кода:
Java:
public static String hex(String message) {
    Pattern pattern = Pattern.compile("&#([A-Fa-f0-9]{6})");
    Matcher matcher = pattern.matcher(message);
    StringBuffer buffer = new StringBuffer();
    while (matcher.find()) {
      String color = matcher.group(1);
      StringBuilder replacement = new StringBuilder("§x");
      for (char c : color.toCharArray())
        replacement.append('§').append(c);
      matcher.appendReplacement(buffer, replacement.toString());
    }
    matcher.appendTail(buffer);
    return buffer.toString().replace("&", "§");
}
Почему это плохо?
Чтож, на самом деле тут много чего, однако нас интересует следующая строчка:
Pattern pattern = Pattern.compile("&#([A-Fa-f0-9]{6})");
Как не сложно догадаться - она компилирует паттерн, который используется для хекса. Однако нюанс заключается в том, что компилирование паттернов - не быстрое дело, а происходит это при КАЖДОМ вызове метода. Таким образом, если при помощи данного метода его автор решит перекрасить 200 сообщений - паттерн будет скомпилирован 200 раз!

Решение:
Вместо того, чтобы компилировать паттерн в методе каждый раз когда он вызывается - мы можем вынести паттерн во вне, сделав его константой:
Java:
private static final Pattern PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})"); // обычно константы пишутся в апперкейсе
public static String hex(String message) {
    Matcher matcher = PATTERN.matcher(message);
    StringBuffer buffer = new StringBuffer();
    while (matcher.find()) {
      String color = matcher.group(1);
      StringBuilder replacement = new StringBuilder("§x");
      for (char c : color.toCharArray())
        replacement.append('§').append(c);
      matcher.appendReplacement(buffer, replacement.toString());
    }
    matcher.appendTail(buffer);
    return buffer.toString().replace('&', '§');
}

Примечание: НЕ КОПИРУЙТЕ ЭТОТ КОД В КАЧЕСТВЕ МЕТОДА КОЛОРИЗАЦИИ ДЛЯ СВОИХ ПЛАГИНОВ! Он неэффективен. Мы доберёмся до оптимального варианта позже.

Ситуация 2:
Нам необходимо отправить оповещение всем игрокам на сервере.

Пример плохого кода:
Java:
public void sendBroadcast(Player sender, String broadcastFormat, String message) {
    for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
        onlinePlayer.sendMessage(broadcastFormat.replace("{sender}", sender.getName()).replace("{message}", message));
    }
}

Почему это плохо?
Как и раньше - проблема остаётся. В данном примере внутри цикла for игроку отправляется сообщение, в котором заменяется {sender} на ник игрока, а также {message} на сообщение.
Суть проблемы заключается в том, что при каждой отправке сообщения в нём будут заменяться его части, что опять же - весьма неэффективно. Особенно в больших циклах.

Решение:
Перед циком мы можем создать локальную переменную в виде строки, в которой заранее заменим весь необходимый текст на наш, таким образом сделав это единожды, избежав многократных операций внутри цикла.
Java:
public void sendBroadcast(Player sender, String broadcastFormat, String message) {
    String finalBroadcast = broadcastFormat.replace("{sender}", sender.getName()).replace("{message}", message);
    for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
        onlinePlayer.sendMessage(finalBroadcast);
    }
}


⦁ Пункт 3 - Проблемы с коллекциями.

В данном пункте также можно выделить несколько подпунктов.

А) Некорректное использование коллекций как таковое.

Тут я хотел бы сказать о том, какие коллекции и когда вам стоит использовать.
Вновь сделаем ремарку для не знающих. Коллекции - это наборы объектов, объединённых общей логикой и структурой. (о слава яндексу)

На данный момент нас интересует List и Set.

List - упорядоченный список из всех элементов, которые будут туда занесены.
Set - просто список элментов без какой либо упорядоченности (на самом деле она там есть, но она там своя, не суть) и без возможности наличия там дубликатов

List нам нужно использовать в случаях когда:
1) Нам нужен порядок элементов.
2) Нам нужно получать значения по индексу (по тому, в каком месте в листе находится определенный элемент).
3) У нас могут быть дубликаты.

Set нам нужно использовать в случаях когда:
1) Нам не важна последовательность элементов.
2) Когда нам не нужно получать значения по индексу.
3) Когда нам не нужны дубликаты.

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

Ситуация:
Нам необходимо сделать список запрещённых блоков, которые игроки не смогут ставить

Пример плохого кода:
Java:
// Берём и создаём условный лист, взяв его из конфига
List<String> blocks = getConfig().getStringList("banned-blocks");

@EventHandler
public void onPlace(BlockPlaceEvent event) {
    Material material = event.getBlock().getType();
    if (blocks.contains(material.toString())) {
        event.setCancelled(true);
    }
}
Почему это плохо?
Ответ заключается в принципе работе метода contains у List-а. Чтобы узнать, содержится ли указанный материал в списке - метод будет перебирать его весь, от начала и до конца, пока не найдёт необходимый элемент в листе и не вернёт ответ.
И если лист содержит в себе множество элементов - то для проверка на то, содержится в нём какой либо элемент может занимать много времени.

Решение:
Использовать сет, в котором алгоритм, проверяющий на то, находится ли в нём элемент работает иначе и быстрее.
Java:
// Берём и создаём условный лист, взяв его из конфига
Set<String> blocks = new HashSet<>(getConfig().getStringList("banned-blocks"));

@EventHandler
public void onPlace(BlockPlaceEvent event) {
    Material material = event.getBlock().getType();
    if (blocks.contains(material.toString())) {
        event.setCancelled(true);
    }
}

Б) Утечки памяти из за невнимательности или незнания.

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

Без лишних слов я скажу главное, что вам нужно будет НАВСЕГДА запомнить.
Никогда не храните в коллекции объект Player.

Почему? Ответ прост. Каждый раз при перезаходе в игру объект игрока (тоесть этот самый Player) меняется. Что конкретно меняется я к сожалению не скажу (поскольку мне было лень проверять), однако если вы попытаетесь сохранить игрока в лист и потом сравнить игрока в листе с тем же казалось бы игроком, но после перезахода - они будут разными.
В чём же проблема? Откуда берется утечка? Всё просто. В джаве у нас есть сборщик мусора, что наверное для вас не секрет. И когда игрок перезаходит в обычных условиях - старый объект игрока удаляется сборщиком мусора через время, поскольку ссылок на него (опять же в обычных условиях) более не остаётся. Однако, если вдруг данный объект игрока занесён в какую либо коллекцию - ссылка на него остаётся и сборщик мусора игнорирует данный объект при сборке по очевидным причинам (не ну мало ли он ещё нужен кому-то). Отсюда и возникает утечка, когда один игрок, перезайдя и будучи занесённым в коллекцию 10 раз создаст 10 объектов игрока соответственно, которые в свою очередь весят немало.

Как избежать такой проблемы? Всё просто - вместо того, чтобы заносить в коллекцию игроков как Player вам необходимо заносить туда либо ник игрока, либо его UUID, а после получать игрока по необходимости через Bukkit#getPlayer

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

В) Неиспользование лучших коллекций.

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

В первую очередь очень хочу сказать про EnumSet и EnumMap.
Если вам необходимо создать список к примеру материалов - не поленитесь и создайте не обычный HashSet<>() с енамами, а специализированный EnumSet.

Пример:
Java:
private Set<Material> createMaterialSet(List<String> stringList) {
        Set<Material> materialSet = EnumSet.noneOf(Material.class);
        for (String s : stringList) {
            Material mat = Material.matchMaterial(s);
            if (mat != null) {
                materialSet.add(mat);
            }
        }
        return materialSet;
    }

Теперь же затронем сторонние библиотеки. В майнкрафте, в самом ядре (а на данный момент я имею в виду Paper, поскольку считаю, что ничто иное просто не должно использоваться в современном мире) присутствуют такие библиотеки как
Google Guava и fastutil, при помощи которых вы можете весьма значительно улучшить свой код.

Так в Google Guava нас интересуют такие коллекции как ImmutableList и ImmutableSet.
В первую очередь их преимущество в том, что в них нельзя ничего положить или удалить после того как они были созданы, что делает их (условно честно говоря) безопасными, если вы опасаетесь за то, что что-то может переписать имеющиеся в них элементы.
Однако в первую очередь - они полезны в случае, когда нам нужно проходиться через них циклом (for). В таком случае они показывают себя в разы быстрее, а потому в случае, если вам необходима коллекция, которую вы создадите единожды, не будете изменять и вам необходимо проходиться по ней циклом - советую использовать одну из этих.

В fastutil же представлены коллекции направленные на более быструю и эффективрую работу с примитивами, такими как int, double, long и т.п.
Если вам необходимо создать List Set или Map, который будет внутри себя содержать Integer и т.п. обёртки, которые могли бы быть примитивами - рекомендую использовать соответствующие IntList, IntSet или Int2ObjectMap-ы.



⦁ Пункт 4 - Асинхрон и что там должно и не должно быть.

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

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

Вот те случаи когда асинхрон нужен:
1) При работе с базами данных, поскольку обработка запросов занимает много времени. Это безусловный принцип работы с базами, который вам необходимо знать.
2) При работе с вебхуками (подключения к сайтам), по той же причине, которая была с базами данных - скорость. В ряде популярных плагинов проверка обновлений не была вынесена из основного потока, что приводило к невероятно медленному запуску.
3) Частая работа с файлами (сохранение в первую очередь).
4) Переодически выполняемые задачи по типу: Регулярной отправки сообщений, тайтлов, звуков, партиклов. Не смотря на то, что это занимает достаточно мало времени - если у нас есть возможность делать это вне основного потока - то почему бы и нет? В примео можно привести те же самые авто-сообщения.


⦁ Пункт 5 - Принцип DRY и ради бога - следуйте ему.

И вновь восславим яндекс.
DRY (Dont Repeat Yorself) - это принцип разработки программного обеспечения, который гласит:
"Не повторяй себя".

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

Пример плохого кода:
Java:
@EventHandler
public void onItemDrop(PlayerDropItemEvent e) {
  if (!event.getPlayer().isOp() && !event.getPlayer().hasPermission("bypass.perm")) {
      event.setCancelled(true);
  }
}

@EventHandler
public void onItemPickup(PlayerPickupItemEvent e) {
  if (!event.getPlayer().isOp() && !event.getPlayer().hasPermission("bypass.perm")) {
      event.setCancelled(true);
  }
}

Исправленный код:
Java:
@EventHandler
public void onItemDrop(PlayerDropItemEvent e) {
  if (!canPickup(event.getPlayer())) {
      event.setCancelled(true);
  }
}

@EventHandler
public void onItemPickup(PlayerPickupItemEvent e) {
  if (!canPickup(event.getPlayer())) {
      event.setCancelled(true);
  }
}

provate boolean canPickup(Player player) {
  return player.isOp() || player.hasPermission("bypass.perm")
}

Либо, доже лучший аналог:
Java:
@EventHandler
public void onItemDrop(PlayerDropItemEvent e) {
      processCheck(e.getPlayer(), e);
}

@EventHandler
public void onItemPickup(PlayerPickupItemEvent e) {
      processCheck(e.getPlayer(), e);
}

private void processCheck(Player p, Cancellable e) {
  if (p.isOp() || p.hasPermission("bypass.perm")) {
        e.setCancelled(true);
    }
}


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


⦁ Пункт 6 - Мелкие придирки к бесящим вещам.

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

А) Синглтоны будь они не ладны.

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

Пример того, как делать не надо:
Java:
public class Main implements JavaPlugin {

    private static Main instance;

    public void onEnable() {
          instance = this;
          getCommand("command").setExecutor(new CommandClass());
    }

    public static Main getInstance() {
        return this.instance;
    }
}
Java:
public class CommandClass implements CommandExecutor {
   public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
      sender.sendMessage(Main.getInstance().getConfig().getString("message"));
   }
}

Почему это плохо?
Чтож, как уже говорилось выше - принцип DRY. Писать каждый раз Main.getInstance() - неэффективно, делает код более громоздким и неудобным в чтении.

Что лучше использовать:
Java:
public class Main implements JavaPlugin {

    public void onEnable() {
        getCommand("command").setExecutor(new CommandClass(this)); // Создаём новый командный класс вот тут
    }

}
Java:
public class CommandClass implements CommandExecutor {
   private final Main plugin;
   public CommandClass(Main plugin) {
       this.plugin = plugin; // инициализируем в конструкторе наш плагин, вместо того, чтобы получать его
   }
   public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
      sender.sendMessage(plugin.getConfig().getString("message")); // используем тут то, что мы инициализировали в конструкторе
   }
}

Б) HEX цвета и их парсинг.

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

А взамен я могу предложить следующий код:
Java:
private static final Pattern HEX_PATTERN = Pattern.compile("&#([a-fA-F\\d]{6})");

    public string String colorize(String message) {
        if (message == null || message.isEmpty()) {
            return message;
        }
        final Matcher matcher = HEX_PATTERN.matcher(message);
        final StringBuilder builder = new StringBuilder(message.length() + 32);
        while (matcher.find()) {
            String group = matcher.group(1);
            matcher.appendReplacement(builder,
                    COLOR_CHAR + "x" +
                            COLOR_CHAR + group.charAt(0) +
                            COLOR_CHAR + group.charAt(1) +
                            COLOR_CHAR + group.charAt(2) +
                            COLOR_CHAR + group.charAt(3) +
                            COLOR_CHAR + group.charAt(4) +
                            COLOR_CHAR + group.charAt(5));
        }
        message = matcher.appendTail(builder).toString();
        return Utils.translateAlternateColorCodes('&', message);
    }

    public static final char COLOR_CHAR = '§';

    public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
        final char[] b = textToTranslate.toCharArray();

        for (int i = 0, length = b.length - 1; i < length; ++i) {
            if (b[i] == altColorChar && isValidColorCharacter(b[i + 1])) {
                b[i++] = COLOR_CHAR;
                b[i] |= 0x20;
            }
        }

        return new String(b);
    }

    private static boolean isValidColorCharacter(char c) {
        return (c >= '0' && c <= '9') ||
                (c >= 'a' && c <= 'f') ||
                c == 'r' ||
                (c >= 'k' && c <= 'o') ||
                c == 'x' ||
                (c >= 'A' && c <= 'F') ||
                c == 'R' ||
                (c >= 'K' && c <= 'O') ||
                c == 'X';
    }

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

В) И ещё немножко про DRY и локальные переменные.

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

Пример плохого кода:
Java:
getCommand("command").setExecutor(new CommandClassAndTabCompleteClassAlso(this));
getCommand("command").setTabCompleter(new CommandClassAndTabCompleteClassAlso(this));

Почему это плохо?
Чтож, во первых мы 2жды дублируем код и 2жды создаём новый объект класса. Казалось бы зачем?

Исправленный код:
Java:
PluginCommand command = getCommand("command");
CommandClassAndTabCompleteClassAlso commandClass = new CommandClassAndTabCompleteClassAlso(this);
command.setExecutor(commandClass);
command.setTabCompleter(commandClass);

В) Игнорирование игронирования.

На самом деле достаточно глупая ошибка, но тем не менее.
Если ваш ивент не подразумевает то, что его действия должны выполняться при его отмене - используйте @EventHandler(ignoreCancelled = true), во избежание лишних действий в неподходящий момент.
Никому не нужно, чтобы плагин на, ну допустим, разлетающиеся во все стороны партиклы при ударе игрока другим игроком работал в зоне, где не работает PVP. Всегда держите это в уме.

Д) System.out.println

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

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

Что использовать вместо? Безусловно логгер плагина через plugin.getLogger().info, который будет выводить имя плагина перед сообщением лога. Удобно? Вполне.
Также стоит упомянуть и plugin.getSlf4Logger и его дополненный аналог из современных версий Paper plugin.getComponentLogger, которые могут выступить лучшими альтернативами, а в особенности ComponentLogger, который полностью совместим с новой системой цветов, чего не скажешь о простом getLogger-е.

Е) Отсутствие исходников

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

Ж) Излишнее следование подсказкам от Idea. (А ё кстати не существует хихи хаха)

Многие начинающие разработчики за частую весьма бездумно следуют подсказкам, которые им высвечивает intellij idea. В целом, эти подсказки полезны и им нужно следовать, однако вы должны знать, что вы делаете.

Примеры плохого кода:
Java:
// 1 плохой пример
String[] stringArray = Objects.requireNonNull(config.getString("value")).split(";");
// 2 плохой пример
ConfigurationSection section = config.getConfigurationSection("section");
assert allowedBookCharsNotify != null;
boolean bool = section.getBoolean("bool");

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

И) Написание всего кода в едином классе или использование огромных методов.

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


⦁ Заключение

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

Если же у вас есть подобные примеры стандартных ошибок, которые совершают новички - милости прошу в обсуждения, добавлю сюда наиболее важное из предложенного.
Автор
Overwrite
Просмотры
980
Первый выпуск
Обновление
Оценка
5.00 звёзд 8 оценок

Другие ресурсы пользователя Overwrite

Поделиться ресурсом

Последние обновления

  1. Дополнения

    Пункт и) в придирках с информацией о том, что не нужно писать всё в главном классе
  2. Дополнения

    Пункт Ж в придирках - про подсказки от idea Небольшие исправления ошибок в форматировании кода...
  3. Дополнения

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

Последние рецензии

Эммм нуууу крутооо пасибааа (надо 50 символов набрать просто), а если серьёзно, всё вполне качественно и хорошо написано.
я тоже тебе поставлю, че уж
(просто текст, чтобы набрать 50 символов)
Overwrite
Overwrite
легчайшая для величайшего
от величайшему равного
Хороший гайд для новичков в сфере разработки майнкрафт плагинов, таких гайдов сейчас мало)
Видно, автор постарался над данной статьёй и потратил больше количество времени, сил на его создание и оформление. Очень рад, что есть креативные люди на форуме. Да и плагины тоже интересные
Ну наконец то, нам начинающим вместо того чтоб критиковать объяснили! Спасибо вам дорогой Overwrite, от лица начинающих разработчиков говорю вам большое спасибо!
Топовая статья, как всегда, впрочем. Спасибо за такой полезный материал.
Имбуля, взял несколько идей на заметку. Надеюсь, русские разработчики хотя бы взглянут.
Наконец-то хоть кто-то поднял эту тему, ура! Жаль конечно что большинство "разработчиков" на неё даже не глянет, но расписано действительно очень классно
Overwrite
Overwrite
Давненько не был в эфире и все наверняка забыли, что только лишь Оверврайт остаётся на стиле!
Назад
Сверху Снизу