Skip to content

Глава 1.12: Чего НЕ существует (Ловушки)

Главная | << Предыдущая: Обработка ошибок | Ловушки | Следующая: Функции и методы >>



Содержание


Полный справочник по ловушкам

1. Нет тернарного оператора

Что вы бы написали:

c
int x = (condition) ? valueA : valueB;

Что происходит: Ошибка компиляции. Оператор ? : не существует.

Правильное решение:

c
int x;
if (condition)
    x = valueA;
else
    x = valueB;

2. Нет цикла do...while

Что вы бы написали:

c
do {
    Process();
} while (HasMore());

Что происходит: Ошибка компиляции. Ключевое слово do не существует.

Правильное решение --- паттерн с флагом:

c
bool first = true;
while (first || HasMore())
{
    first = false;
    Process();
}

Правильное решение --- паттерн с break:

c
while (true)
{
    Process();
    if (!HasMore())
        break;
}

3. Нет try/catch/throw

Что вы бы написали:

c
try {
    RiskyOperation();
} catch (Exception e) {
    HandleError(e);
}

Что происходит: Ошибка компиляции. Эти ключевые слова не существуют.

Правильное решение: Защитные выражения с ранним возвратом.

c
void DoOperation()
{
    if (!CanDoOperation())
    {
        ErrorEx("Cannot perform operation", ErrorExSeverity.WARNING);
        return;
    }

    // Выполнять безопасно
    RiskyOperation();
}

См. Главу 1.11 --- Обработка ошибок для полных паттернов.


4. Нет множественного наследования

Что вы бы написали:

c
class MyClass extends BaseA, BaseB  // Два базовых класса

Что происходит: Ошибка компиляции. Поддерживается только одиночное наследование.

Правильное решение: Наследуйте от одного класса, используйте композицию для другого:

c
class MyClass extends BaseA
{
    ref BaseB m_Helper;

    void MyClass()
    {
        m_Helper = new BaseB();
    }
}

5. Нет перегрузки операторов (кроме индекса)

Что вы бы написали:

c
Vector3 operator+(Vector3 a, Vector3 b) { ... }
bool operator==(MyClass other) { ... }

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

Правильное решение: Используйте именованные методы:

c
class MyVector
{
    float x, y, z;

    MyVector Add(MyVector other)
    {
        MyVector result = new MyVector();
        result.x = x + other.x;
        result.y = y + other.y;
        result.z = z + other.z;
        return result;
    }

    bool Equals(MyVector other)
    {
        return (x == other.x && y == other.y && z == other.z);
    }
}

Исключение: Оператор индекса [] может быть перегружен через методы Get(index) и Set(index, value):

c
class MyContainer
{
    int data[10];

    int Get(int index) { return data[index]; }
    void Set(int index, int value) { data[index] = value; }
}

MyContainer c = new MyContainer();
c[3] = 42;        // Вызывает Set(3, 42)
int v = c[3];     // Вызывает Get(3)

6. Нет лямбд / анонимных функций

Что вы бы написали:

c
array.Sort((a, b) => a.name.CompareTo(b.name));
button.OnClick += () => { DoSomething(); };

Что происходит: Ошибка компиляции. Синтаксис лямбд не существует.

Правильное решение: Определите именованные методы и передавайте их как ScriptCaller или используйте строковые обратные вызовы:

c
// Именованный метод
void OnButtonClick()
{
    DoSomething();
}

// Строковый обратный вызов (используется CallLater, таймерами и т.д.)
GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(this.OnButtonClick, 1000, false);

7. Нет делегатов / указателей на функции (нативных)

Что вы бы написали:

c
delegate void MyCallback(int value);
MyCallback cb = SomeFunction;
cb(42);

Что происходит: Ошибка компиляции. Ключевое слово delegate не существует.

Правильное решение: Используйте ScriptCaller, ScriptInvoker или строковые имена методов:

c
// ScriptCaller (одиночный обратный вызов)
ScriptCaller caller = ScriptCaller.Create(MyFunction);

// ScriptInvoker (событие с несколькими подписчиками)
ref ScriptInvoker m_OnEvent = new ScriptInvoker();
m_OnEvent.Insert(MyHandler);
m_OnEvent.Invoke();  // Вызывает все зарегистрированные обработчики

8. Нет escape для обратной косой черты/кавычки

Что вы бы написали:

c
string path = "C:\\Users\\folder";
string quote = "He said \"hello\"";

Что происходит: CParser крашится или выдаёт искажённый вывод. Escape-последовательности \\ и \" ломают парсер строк.

Правильное решение: Полностью избегайте символов обратного слэша и кавычек в строковых литералах:

c
// Используйте прямые слэши для путей
string path = "C:/Users/folder";

// Используйте одинарные кавычки или перефразируйте, чтобы избежать вложенных двойных кавычек
string quote = "He said 'hello'";

// Используйте конкатенацию строк, если абсолютно необходимы специальные символы
// (всё равно рискованно --- тестируйте тщательно)

Примечание: Escape-последовательности \n, \r и \t РАБОТАЮТ. Только \\ и \" сломаны.


9. Нет переобъявления переменных в блоках else-if

Что вы бы написали:

c
if (condA)
{
    string msg = "Case A";
    Print(msg);
}
else if (condB)
{
    string msg = "Case B";  // То же имя переменной в смежном блоке
    Print(msg);
}

Что происходит: Ошибка компиляции: «multiple declaration of variable 'msg'». Enforce Script считает переменные в смежных блоках if/else if/else принадлежащими одной области видимости.

Правильное решение --- уникальные имена:

c
if (condA)
{
    string msgA = "Case A";
    Print(msgA);
}
else if (condB)
{
    string msgB = "Case B";
    Print(msgB);
}

Правильное решение --- объявление перед if:

c
string msg;
if (condA)
{
    msg = "Case A";
}
else if (condB)
{
    msg = "Case B";
}
Print(msg);

10. Нет тернарного оператора в объявлении переменных

Связано с ловушкой #1, но специфично для объявлений:

Что вы бы написали:

c
string label = isAdmin ? "Admin" : "Player";

Правильное решение:

c
string label;
if (isAdmin)
    label = "Admin";
else
    label = "Player";

11. Object.IsAlive() НЕ существует у базового Object

Что вы бы написали:

c
Object obj = GetSomething();
if (obj.IsAlive())  // Проверка жив ли

Что происходит: Ошибка компиляции или краш в рантайме. IsAlive() определён в EntityAI, а не в Object.

Правильное решение:

c
Object obj = GetSomething();
EntityAI eai;
if (Class.CastTo(eai, obj) && eai.IsAlive())
{
    // Подтверждено --- жив
}

12. Нет nullptr --- используйте NULL или null

Что вы бы написали:

c
if (obj == nullptr)

Что происходит: Ошибка компиляции. Ключевое слово nullptr не существует.

Правильное решение:

c
if (obj == null)    // в нижнем регистре работает
if (obj == NULL)    // в верхнем регистре тоже работает
if (!obj)           // идиоматическая проверка на null (предпочтительно)

13. switch/case НЕ проваливается

Что вы бы написали (ожидая поведение C/C++ fall-through):

c
switch (value)
{
    case 1:
    case 2:
    case 3:
        Print("1, 2, or 3");  // В C++ кейсы 1 и 2 проваливаются сюда
        break;
}

Что происходит: Только case 3 выполняет Print. Кейсы 1 и 2 пусты --- они ничего не делают и НЕ проваливаются.

Правильное решение:

c
if (value >= 1 && value <= 3)
{
    Print("1, 2, or 3");
}

// Или обрабатывайте каждый кейс явно:
switch (value)
{
    case 1:
        Print("1, 2, or 3");
        break;
    case 2:
        Print("1, 2, or 3");
        break;
    case 3:
        Print("1, 2, or 3");
        break;
}

Примечание: break технически необязателен в Enforce Script, так как проваливания нет, но его принято включать.


14. Нет выражений для параметров по умолчанию

Что вы бы написали:

c
void Spawn(vector pos = GetDefaultPos())    // Выражение как значение по умолчанию
void Spawn(vector pos = Vector(0, 100, 0))  // Конструктор как значение по умолчанию

Что происходит: Ошибка компиляции. Значения параметров по умолчанию должны быть литералами или NULL.

Правильное решение:

c
void Spawn(vector pos = "0 100 0")    // Строковый литерал для vector --- OK
void Spawn(int count = 5)             // Целочисленный литерал --- OK
void Spawn(float radius = 10.0)      // Литерал float --- OK
void Spawn(string name = "default")   // Строковый литерал --- OK
void Spawn(Object obj = NULL)         // NULL --- OK

// Для сложных значений по умолчанию используйте перегрузки:
void Spawn()
{
    Spawn(GetDefaultPos());  // Вызов параметрической версии
}

void Spawn(vector pos)
{
    // Фактическая реализация
}

15. JsonFileLoader.JsonLoadFile возвращает void

Что вы бы написали:

c
MyConfig cfg = JsonFileLoader<MyConfig>.JsonLoadFile(path);
// или:
if (JsonFileLoader<MyConfig>.JsonLoadFile(path, cfg))

Что происходит: Ошибка компиляции. JsonLoadFile возвращает void, а не загруженный объект или bool.

Правильное решение:

c
MyConfig cfg = new MyConfig();  // Сначала создать экземпляр со значениями по умолчанию
JsonFileLoader<MyConfig>.JsonLoadFile(path, cfg);  // Заполняет cfg на месте
// cfg теперь содержит загруженные значения (или по-прежнему имеет значения по умолчанию, если файл был невалиден)

Примечание: Более новый метод JsonFileLoader<T>.LoadFile() возвращает bool, но JsonLoadFile (часто встречающаяся версия) --- нет.


16. Нет подстановки значений #define

Что вы бы написали:

c
#define MAX_PLAYERS 60
#define VERSION_STRING "1.0.0"
int max = MAX_PLAYERS;

Что происходит: Ошибка компиляции. #define в Enforce Script создаёт только флаги существования для проверок #ifdef. Подстановка значений не поддерживается.

Правильное решение:

c
// Используйте const для значений
const int MAX_PLAYERS = 60;
const string VERSION_STRING = "1.0.0";

// Используйте #define только для флагов условной компиляции
#define MY_MOD_ENABLED

17. Нет интерфейсов / абстрактных классов (принудительно)

Что вы бы написали:

c
interface ISerializable
{
    void Serialize();
    void Deserialize();
}

abstract class BaseProcessor
{
    abstract void Process();
}

Что происходит: Ключевые слова interface и abstract не существуют.

Правильное решение: Используйте обычные классы с пустыми базовыми методами:

c
// «Интерфейс» --- базовый класс с пустыми методами
class ISerializable
{
    void Serialize() {}     // Переопределите в подклассе
    void Deserialize() {}   // Переопределите в подклассе
}

// «Абстрактный» класс --- тот же паттерн
class BaseProcessor
{
    void Process()
    {
        ErrorEx("BaseProcessor.Process() must be overridden!", ErrorExSeverity.ERROR);
    }
}

class ConcreteProcessor extends BaseProcessor
{
    override void Process()
    {
        // Фактическая реализация
    }
}

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


18. Нет ограничений для дженериков

Что вы бы написали:

c
class Container<T> where T : EntityAI  // Ограничить T до EntityAI

Что происходит: Ошибка компиляции. Конструкция where не существует. Параметры шаблона принимают любой тип.

Правильное решение: Валидируйте в рантайме:

c
class EntityContainer<Class T>
{
    void Add(T item)
    {
        // Проверка типа в рантайме вместо ограничения на этапе компиляции
        EntityAI eai;
        if (!Class.CastTo(eai, item))
        {
            ErrorEx("EntityContainer only accepts EntityAI subclasses");
            return;
        }
        // продолжить
    }
}

19. Нет валидации enum

Что вы бы написали:

c
EDamageState state = (EDamageState)999;  // Ожидаете ошибку или исключение

Что происходит: Без ошибки. Любое значение int может быть присвоено переменной enum, даже значения за пределами определённого диапазона.

Правильное решение: Валидируйте вручную:

c
bool IsValidDamageState(int value)
{
    return (value >= EDamageState.PRISTINE && value <= EDamageState.RUINED);
}

int rawValue = LoadFromConfig();
if (IsValidDamageState(rawValue))
{
    EDamageState state = rawValue;
}
else
{
    Print("Invalid damage state: " + rawValue.ToString());
    EDamageState state = EDamageState.PRISTINE;  // запасное значение
}

20. Нет вариативных параметров

Что вы бы написали:

c
void Log(string format, params object[] args)
void Printf(string fmt, ...)

Что происходит: Ошибка компиляции. Вариативные параметры не существуют.

Правильное решение: Используйте string.Format с фиксированным количеством параметров или классы Param:

c
// string.Format поддерживает до 9 позиционных аргументов
string msg = string.Format("Player %1 at %2 with %3 HP", name, pos, hp);

// Для данных переменной длины передавайте массив
void LogMultiple(string tag, array<string> messages)
{
    foreach (string msg : messages)
    {
        Print("[" + tag + "] " + msg);
    }
}

21. Нет вложенных объявлений классов

Что вы бы написали:

c
class Outer
{
    class Inner  // Вложенный класс
    {
        int value;
    }
}

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

Правильное решение: Объявляйте все классы на верхнем уровне, используйте соглашения об именовании для отображения связей:

c
class MySystem_Config
{
    int value;
}

class MySystem
{
    ref MySystem_Config m_Config;
}

22. Статические массивы фиксированного размера

Что вы бы написали:

c
int size = GetCount();
int arr[size];  // Динамический размер в рантайме

Что происходит: Ошибка компиляции. Размеры статических массивов должны быть константами времени компиляции.

Правильное решение:

c
// Используйте const для статических массивов
const int BUFFER_SIZE = 64;
int arr[BUFFER_SIZE];

// Или используйте динамические массивы для задания размера в рантайме
array<int> arr = new array<int>;
arr.Resize(GetCount());

23. array.Remove не сохраняет порядок

Что вы бы написали (ожидая сохранение порядка):

c
array<string> items = {"A", "B", "C", "D"};
items.Remove(1);  // Ожидаете: {"A", "C", "D"}

Что происходит: Remove(index) меняет элемент местами с последним элементом, затем удаляет последний. Результат: {"A", "D", "C"}. Порядок НЕ сохраняется.

Правильное решение:

c
// Используйте RemoveOrdered для сохранения порядка (медленнее --- сдвигает элементы)
items.RemoveOrdered(1);  // {"A", "C", "D"} --- правильный порядок

// Используйте RemoveItem для поиска и удаления по значению (тоже упорядоченно)
items.RemoveItem("B");   // {"A", "C", "D"}

24. Нет #include --- всё через config.cpp

Что вы бы написали:

c
#include "MyHelper.c"
#include "Utils/StringUtils.c"

Что происходит: Нет эффекта или ошибка компиляции. Директива #include не существует.

Правильное решение: Все файлы скриптов загружаются через config.cpp в записи CfgMods мода. Порядок загрузки определяется слоем скриптов (3_Game, 4_World, 5_Mission) и алфавитным порядком внутри каждого слоя.

cpp
// config.cpp
class CfgMods
{
    class MyMod
    {
        type = "mod";
        dependencies[] = { "Game", "World", "Mission" };

        class defs
        {
            class gameScriptModule
            {
                files[] = { "MyMod/Scripts/3_Game" };
            };
            class worldScriptModule
            {
                files[] = { "MyMod/Scripts/4_World" };
            };
            class missionScriptModule
            {
                files[] = { "MyMod/Scripts/5_Mission" };
            };
        };
    };
};

25. Нет пространств имён

Что вы бы написали:

c
namespace MyMod { class Config { } }
namespace MyMod.Utils { class StringHelper { } }

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

Правильное решение: Используйте префиксы имён для избежания конфликтов:

c
class MyConfig { }          // MyFramework
class MyAI_Config { }       // MyAI Mod
class MyM_MissionData { }   // MyMissions Mod
class VPP_AdminConfig { }     // VPP Admin

26. Методы строк изменяют на месте

Что вы бы написали (ожидая возвращаемое значение):

c
string upper = myString.ToUpper();  // Ожидаете: возвращает новую строку

Что происходит: ToUpper() и ToLower() изменяют строку на месте и возвращают void.

Правильное решение:

c
// Сначала сделайте копию, если нужно сохранить оригинал
string original = "Hello World";
string upper = original;
upper.ToUpper();  // upper теперь "HELLO WORLD", original не изменён

// То же самое для TrimInPlace
string trimmed = "  hello  ";
trimmed.TrimInPlace();  // "hello"

27. Циклы ref вызывают утечки памяти

Что вы бы написали:

c
class Parent
{
    ref Child m_Child;
}
class Child
{
    ref Parent m_Parent;  // Циклическая ссылка --- оба ссылаются друг на друга
}

Что происходит: Ни один объект никогда не будет удалён сборщиком мусора. Счётчики ссылок никогда не достигнут нуля, потому что каждый хранит ref на другого.

Правильное решение: Одна сторона должна использовать сырой (не-ref) указатель:

c
class Parent
{
    ref Child m_Child;  // Родитель ВЛАДЕЕТ дочерним (ref)
}
class Child
{
    Parent m_Parent;    // Дочерний ССЫЛАЕТСЯ на родителя (сырой --- без ref)
}

28. Нет гарантии деструктора при остановке сервера

Что вы бы написали (ожидая очистку):

c
void ~MyManager()
{
    SaveData();  // Ожидаете, что это выполнится при завершении работы
}

Что происходит: Завершение работы сервера может убить процесс до вызова деструкторов. Ваше сохранение не происходит.

Правильное решение: Сохраняйте проактивно через регулярные интервалы и при известных событиях жизненного цикла:

c
class MyManager
{
    void OnMissionFinish()  // Вызывается перед завершением
    {
        SaveData();  // Надёжная точка сохранения
    }

    void OnUpdate(float dt)
    {
        m_SaveTimer += dt;
        if (m_SaveTimer > 300.0)  // Каждые 5 минут
        {
            SaveData();
            m_SaveTimer = 0;
        }
    }
}

29. Нет управления ресурсами на основе области видимости (RAII)

Что вы бы написали (в C++):

c
{
    FileHandle f = OpenFile("test.txt", FileMode.WRITE);
    // f автоматически закрывается при выходе из области видимости
}

Что происходит: Enforce Script не закрывает файловые дескрипторы при выходе переменных из области видимости (даже с autoptr).

Правильное решение: Всегда закрывайте ресурсы явно:

c
FileHandle fh = OpenFile("$profile:MyMod/data.txt", FileMode.WRITE);
if (fh != 0)
{
    FPrintln(fh, "data");
    CloseFile(fh);  // Нужно закрыть вручную!
}

30. GetGame().GetPlayer() возвращает null на сервере

Что вы бы написали:

c
PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
player.DoSomething();  // КРАШ на сервере!

Что происходит: GetGame().GetPlayer() возвращает локального игрока. На выделенном сервере локального игрока нет --- возвращается null.

Правильное решение: На сервере перебирайте список игроков:

c
#ifdef SERVER
    array<Man> players = new array<Man>;
    GetGame().GetPlayers(players);
    foreach (Man man : players)
    {
        PlayerBase player;
        if (Class.CastTo(player, man))
        {
            player.DoSomething();
        }
    }
#else
    PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
    if (player)
    {
        player.DoSomething();
    }
#endif

31. Классы sealed нельзя расширять (1.28+)

Начиная с DayZ 1.28, компилятор Enforce Script принудительно применяет ключевое слово sealed. Класс или метод, помеченный sealed, не может быть унаследован или переопределён. Если вы попытаетесь расширить sealed-класс:

c
// Если BI пометил SomeVanillaClass как sealed:
class MyClass : SomeVanillaClass  // ОШИБКА КОМПИЛЯЦИИ в 1.28+
{
}

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


32. Ограничение параметров метода: максимум 16 (1.28+)

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

c
// ОШИБКА КОМПИЛЯЦИИ в 1.28+ --- превышает 16 параметров
void MyMethod(int a, int b, int c, int d, int e, int f, int g, int h,
              int i, int j, int k, int l, int m, int n, int o, int p,
              int q)  // 17-й параметр = ошибка
{
}

Решение: Рефакторинг --- передавайте класс или массив вместо отдельных параметров.


33. Предупреждения атрибута Obsolete (1.28+)

В DayZ 1.28 появился атрибут Obsolete. Функции и классы, помеченные [Obsolete], генерируют предупреждения компилятора. Эти API по-прежнему работают, но запланированы к удалению в будущем обновлении. Проверяйте вывод сборки на предупреждения об устаревших элементах и мигрируйте на рекомендуемые замены.


34. Ошибка сравнения int.MIN

Сравнения с участием int.MIN (-2147483648) дают неправильные результаты:

c
int val = 1;
if (val < int.MIN)  // Вычисляется как TRUE --- должно быть false
{
    // Этот блок выполняется некорректно
}

Избегайте прямых сравнений с int.MIN. Используйте сохранённую константу или сравнивайте с конкретным отрицательным значением.


35. Логическое отрицание элемента массива не работает

Прямое логическое отрицание элементов массива не компилируется:

c
array<int> list = {0, 1, 2};
if (!list[1])          // НЕ КОМПИЛИРУЕТСЯ
if (list[1] == 0)      // Работает --- используйте явное сравнение

Всегда используйте явные проверки на равенство при тестировании элементов массива на истинность.


36. Сложное выражение при присваивании в массив вызывает краш

Присваивание сложного выражения напрямую в элемент массива может вызвать segmentation fault:

c
// КРАШИТСЯ в рантайме
m_Values[index] = vector.DistanceSq(posA, posB) <= distSq;

// БЕЗОПАСНО --- используйте промежуточную переменную
bool result = vector.DistanceSq(posA, posB) <= distSq;
m_Values[index] = result;

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


37. foreach на возвращаемом значении метода вызывает краш

Использование foreach напрямую на возвращаемом значении метода вызывает null pointer exception на второй итерации:

c
// КРАШИТСЯ на 2-м элементе
foreach (string item : GetMyArray())
{
}

// БЕЗОПАСНО --- сначала сохраните в локальную переменную
array<string> items = GetMyArray();
foreach (string item : items)
{
}

38. Приоритет побитовых операторов и операторов сравнения

Побитовые операторы имеют более низкий приоритет, чем операторы сравнения, следуя правилам C/C++:

c
int flags = 5;
int mask = 4;
if (flags & mask == mask)      // НЕПРАВИЛЬНО: вычисляется как flags & (mask == mask)
if ((flags & mask) == mask)    // ПРАВИЛЬНО: всегда используйте скобки

39. Пустые блоки #ifdef / #ifndef вызывают краш

Пустые блоки условной компиляции --- даже содержащие только комментарии --- вызывают segmentation fault:

c
#ifdef SOME_DEFINE
    // Этот блок только с комментарием вызывает SEGFAULT
#endif

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


40. GetGame().IsClient() возвращает false при загрузке

Во время фазы загрузки клиента GetGame().IsClient() возвращает false, а GetGame().IsServer() возвращает true --- даже на клиентах. Используйте IsDedicatedServer() вместо этого:

c
// НЕНАДЁЖНО во время фазы загрузки
if (GetGame().IsClient()) { }   // false при загрузке!
if (GetGame().IsServer()) { }   // true при загрузке, даже на клиенте!

// НАДЁЖНО
if (!GetGame().IsDedicatedServer()) { /* клиентский код */ }
if (GetGame().IsDedicatedServer())  { /* серверный код */ }

Исключение: Если вам нужна поддержка офлайн/одиночного режима, IsDedicatedServer() возвращает false и для listen-серверов.


41. Сообщения об ошибках компиляции указывают на неправильный файл

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


42. Файлы crash_*.log --- это не краши

Лог-файлы с именами crash_<date>_<time>.log содержат исключения времени выполнения, а не фактические segmentation fault. Название вводит в заблуждение --- это скриптовые ошибки, а не краши движка.


Переход с C++

Если вы разработчик C++, вот самые большие различия:

Возможность C++Эквивалент в Enforce Script
std::vectorarray<T>
std::mapmap<K,V>
std::unique_ptrref / autoptr
dynamic_cast<T*>Class.CastTo() или T.Cast()
try/catchЗащитные выражения
operator+Именованные методы (Add())
namespaceПрефиксы имён (My, VPP_)
#includeconfig.cpp files[]
RAIIРучная очистка в методах жизненного цикла
Множественное наследованиеОдиночное наследование + композиция
nullptrnull / NULL
Шаблоны с ограничениямиШаблоны без ограничений + проверки в рантайме
do...whilewhile (true) { ... if (!cond) break; }

Переход с C#

Возможность C#Эквивалент в Enforce Script
interfaceБазовый класс с пустыми методами
abstractБазовый класс + ErrorEx в базовых методах
delegate / eventScriptInvoker
Лямбда =>Именованные методы
?. null conditionalРучные проверки на null
?? null coalescingif (!x) x = default;
try/catchЗащитные выражения
using (IDisposable)Ручная очистка
Свойства { get; set; }Публичные поля или явные геттеры/сеттеры
LINQРучные циклы
nameof()Жёстко заданные строки
async/awaitCallLater / таймеры

Переход с Java

Возможность JavaЭквивалент в Enforce Script
interfaceБазовый класс с пустыми методами
try/catch/finallyЗащитные выражения
Сборка мусораref + подсчёт ссылок (нет GC для циклов)
@OverrideКлючевое слово override
instanceofobj.IsInherited(typename)
packageПрефиксы имён
importconfig.cpp files[]
enum с методамиenum (только int) + вспомогательный класс
finalconst (только для переменных)
АннотацииНе доступны

Переход с Python

Возможность PythonЭквивалент в Enforce Script
Динамическая типизацияСтатическая типизация (все переменные типизированы)
try/exceptЗащитные выражения
lambdaИменованные методы
List comprehensionРучные циклы
**kwargs / *argsФиксированные параметры
Утиная типизацияIsInherited() / Class.CastTo()
__init__Конструктор (то же имя, что и класс)
__del__Деструктор (~ClassName())
importconfig.cpp files[]
Множественное наследованиеОдиночное наследование + композиция
Nonenull / NULL
Блоки на основе отступовФигурные скобки { }
f-строкиstring.Format("text %1 %2", a, b)

Краткая справочная таблица

ВозможностьЕсть?Обходной путь
Тернарный ? :Нетif/else
do...whileНетwhile + break
try/catchНетЗащитные выражения
Множественное наследованиеНетКомпозиция
Перегрузка операторовТолько индексИменованные методы
ЛямбдыНетИменованные методы
ДелегатыНетScriptInvoker
\\ / \" в строкахСломаноИзбегать
Переобъявление переменныхСломано в else-ifУникальные имена или объявление перед if
Object.IsAlive()Нет на базовом ObjectСначала приведение к EntityAI
nullptrНетnull / NULL
switch fall-throughНетКаждый case независим
Выражения в параметрах по умолчаниюНетТолько литералы или NULL
Значения #defineНетconst
ИнтерфейсыНетПустой базовый класс
Ограничения дженериковНетПроверки типов в рантайме
Валидация enumНетРучная проверка диапазона
Вариативные параметрыНетstring.Format или массивы
Вложенные классыНетВерхний уровень с префиксами
Массивы переменного размераНетarray<T>
#includeНетconfig.cpp files[]
Пространства имёнНетПрефиксы имён
RAIIНетРучная очистка
GetGame().GetPlayer() на сервереВозвращает nullПеребор GetPlayers()
Наследование от sealed класса (1.28+)Ошибка компиляцииИспользуйте композицию
17+ параметров метода (1.28+)Ошибка компиляцииПередавайте класс или массив
API [Obsolete] (1.28+)Предупреждение компилятораМигрируйте на замену
Сравнения с int.MINНеправильные результатыИспользуйте сохранённую константу
Отрицание !array[i]Ошибка компиляцииИспользуйте array[i] == 0
Сложное выражение при присваивании в массивSegfaultИспользуйте промежуточную переменную
foreach на возвращаемом значении методаКраш null pointerСначала сохраните в локальную переменную
Приоритет побитовых операторов и сравненияНеправильное вычислениеВсегда используйте скобки
Пустые блоки #ifdefSegfaultВключите оператор или уберите блок
IsClient() при загрузкеВозвращает falseИспользуйте IsDedicatedServer()
Ошибка компиляции в неправильном файлеЛожное расположениеПроверяйте файл, разобранный после указанного
Файлы crash_*.logНе настоящие крашиЭто исключения скриптов времени выполнения

Навигация

ПредыдущаяВверхСледующая
1.11 Обработка ошибокЧасть 1: Enforce Script1.13 Функции и методы

Released under CC BY-SA 4.0 | Code examples under MIT License