Глава 1.12: Чего НЕ существует (Ловушки)
Главная | << Предыдущая: Обработка ошибок | Ловушки | Следующая: Функции и методы >>
Содержание
- Полный справочник по ловушкам
- Нет тернарного оператора
- Нет цикла do...while
- Нет try/catch/throw
- Нет множественного наследования
- Нет перегрузки операторов (кроме индекса)
- Нет лямбд / анонимных функций
- Нет делегатов / указателей на функции (нативных)
- Нет escape для обратной косой черты/кавычки
- Нет переобъявления переменных в блоках else-if
- Нет тернарного оператора в объявлении переменных
- Object.IsAlive() НЕ существует у базового Object
- Нет nullptr --- используйте NULL или null
- switch/case НЕ проваливается
- Нет выражений для параметров по умолчанию
- JsonFileLoader.JsonLoadFile возвращает void
- Нет подстановки значений #define
- Нет интерфейсов / абстрактных классов (принудительно)
- Нет ограничений для дженериков
- Нет валидации enum
- Нет вариативных параметров
- Нет вложенных объявлений классов
- Статические массивы фиксированного размера
- array.Remove не сохраняет порядок
- Нет #include --- всё через config.cpp
- Нет пространств имён
- Методы строк изменяют на месте
- Циклы ref вызывают утечки памяти
- Нет гарантии деструктора при остановке сервера
- Нет управления ресурсами на основе области видимости (RAII)
- GetGame().GetPlayer() возвращает null на сервере
- Классы
sealedнельзя расширять (1.28+) - Ограничение параметров метода: максимум 16 (1.28+)
- Предупреждения атрибута
Obsolete(1.28+) - Ошибка сравнения
int.MIN - Логическое отрицание элемента массива не работает
- Сложное выражение при присваивании в массив вызывает краш
foreachна возвращаемом значении метода вызывает краш- Приоритет побитовых операторов и операторов сравнения
- Пустые блоки
#ifdef/#ifndefвызывают краш GetGame().IsClient()возвращает false при загрузке- Сообщения об ошибках компиляции указывают на неправильный файл
- Файлы
crash_*.log--- это не краши
- Переход с C++
- Переход с C#
- Переход с Java
- Переход с Python
- Краткая справочная таблица
- Навигация
Полный справочник по ловушкам
1. Нет тернарного оператора
Что вы бы написали:
int x = (condition) ? valueA : valueB;Что происходит: Ошибка компиляции. Оператор ? : не существует.
Правильное решение:
int x;
if (condition)
x = valueA;
else
x = valueB;2. Нет цикла do...while
Что вы бы написали:
do {
Process();
} while (HasMore());Что происходит: Ошибка компиляции. Ключевое слово do не существует.
Правильное решение --- паттерн с флагом:
bool first = true;
while (first || HasMore())
{
first = false;
Process();
}Правильное решение --- паттерн с break:
while (true)
{
Process();
if (!HasMore())
break;
}3. Нет try/catch/throw
Что вы бы написали:
try {
RiskyOperation();
} catch (Exception e) {
HandleError(e);
}Что происходит: Ошибка компиляции. Эти ключевые слова не существуют.
Правильное решение: Защитные выражения с ранним возвратом.
void DoOperation()
{
if (!CanDoOperation())
{
ErrorEx("Cannot perform operation", ErrorExSeverity.WARNING);
return;
}
// Выполнять безопасно
RiskyOperation();
}См. Главу 1.11 --- Обработка ошибок для полных паттернов.
4. Нет множественного наследования
Что вы бы написали:
class MyClass extends BaseA, BaseB // Два базовых классаЧто происходит: Ошибка компиляции. Поддерживается только одиночное наследование.
Правильное решение: Наследуйте от одного класса, используйте композицию для другого:
class MyClass extends BaseA
{
ref BaseB m_Helper;
void MyClass()
{
m_Helper = new BaseB();
}
}5. Нет перегрузки операторов (кроме индекса)
Что вы бы написали:
Vector3 operator+(Vector3 a, Vector3 b) { ... }
bool operator==(MyClass other) { ... }Что происходит: Ошибка компиляции. Пользовательские операторы не могут быть определены.
Правильное решение: Используйте именованные методы:
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):
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. Нет лямбд / анонимных функций
Что вы бы написали:
array.Sort((a, b) => a.name.CompareTo(b.name));
button.OnClick += () => { DoSomething(); };Что происходит: Ошибка компиляции. Синтаксис лямбд не существует.
Правильное решение: Определите именованные методы и передавайте их как ScriptCaller или используйте строковые обратные вызовы:
// Именованный метод
void OnButtonClick()
{
DoSomething();
}
// Строковый обратный вызов (используется CallLater, таймерами и т.д.)
GetGame().GetCallQueue(CALL_CATEGORY_GAMEPLAY).CallLater(this.OnButtonClick, 1000, false);7. Нет делегатов / указателей на функции (нативных)
Что вы бы написали:
delegate void MyCallback(int value);
MyCallback cb = SomeFunction;
cb(42);Что происходит: Ошибка компиляции. Ключевое слово delegate не существует.
Правильное решение: Используйте ScriptCaller, ScriptInvoker или строковые имена методов:
// ScriptCaller (одиночный обратный вызов)
ScriptCaller caller = ScriptCaller.Create(MyFunction);
// ScriptInvoker (событие с несколькими подписчиками)
ref ScriptInvoker m_OnEvent = new ScriptInvoker();
m_OnEvent.Insert(MyHandler);
m_OnEvent.Invoke(); // Вызывает все зарегистрированные обработчики8. Нет escape для обратной косой черты/кавычки
Что вы бы написали:
string path = "C:\\Users\\folder";
string quote = "He said \"hello\"";Что происходит: CParser крашится или выдаёт искажённый вывод. Escape-последовательности \\ и \" ломают парсер строк.
Правильное решение: Полностью избегайте символов обратного слэша и кавычек в строковых литералах:
// Используйте прямые слэши для путей
string path = "C:/Users/folder";
// Используйте одинарные кавычки или перефразируйте, чтобы избежать вложенных двойных кавычек
string quote = "He said 'hello'";
// Используйте конкатенацию строк, если абсолютно необходимы специальные символы
// (всё равно рискованно --- тестируйте тщательно)Примечание: Escape-последовательности
\n,\rи\tРАБОТАЮТ. Только\\и\"сломаны.
9. Нет переобъявления переменных в блоках else-if
Что вы бы написали:
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 принадлежащими одной области видимости.
Правильное решение --- уникальные имена:
if (condA)
{
string msgA = "Case A";
Print(msgA);
}
else if (condB)
{
string msgB = "Case B";
Print(msgB);
}Правильное решение --- объявление перед if:
string msg;
if (condA)
{
msg = "Case A";
}
else if (condB)
{
msg = "Case B";
}
Print(msg);10. Нет тернарного оператора в объявлении переменных
Связано с ловушкой #1, но специфично для объявлений:
Что вы бы написали:
string label = isAdmin ? "Admin" : "Player";Правильное решение:
string label;
if (isAdmin)
label = "Admin";
else
label = "Player";11. Object.IsAlive() НЕ существует у базового Object
Что вы бы написали:
Object obj = GetSomething();
if (obj.IsAlive()) // Проверка жив лиЧто происходит: Ошибка компиляции или краш в рантайме. IsAlive() определён в EntityAI, а не в Object.
Правильное решение:
Object obj = GetSomething();
EntityAI eai;
if (Class.CastTo(eai, obj) && eai.IsAlive())
{
// Подтверждено --- жив
}12. Нет nullptr --- используйте NULL или null
Что вы бы написали:
if (obj == nullptr)Что происходит: Ошибка компиляции. Ключевое слово nullptr не существует.
Правильное решение:
if (obj == null) // в нижнем регистре работает
if (obj == NULL) // в верхнем регистре тоже работает
if (!obj) // идиоматическая проверка на null (предпочтительно)13. switch/case НЕ проваливается
Что вы бы написали (ожидая поведение C/C++ fall-through):
switch (value)
{
case 1:
case 2:
case 3:
Print("1, 2, or 3"); // В C++ кейсы 1 и 2 проваливаются сюда
break;
}Что происходит: Только case 3 выполняет Print. Кейсы 1 и 2 пусты --- они ничего не делают и НЕ проваливаются.
Правильное решение:
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. Нет выражений для параметров по умолчанию
Что вы бы написали:
void Spawn(vector pos = GetDefaultPos()) // Выражение как значение по умолчанию
void Spawn(vector pos = Vector(0, 100, 0)) // Конструктор как значение по умолчаниюЧто происходит: Ошибка компиляции. Значения параметров по умолчанию должны быть литералами или NULL.
Правильное решение:
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
Что вы бы написали:
MyConfig cfg = JsonFileLoader<MyConfig>.JsonLoadFile(path);
// или:
if (JsonFileLoader<MyConfig>.JsonLoadFile(path, cfg))Что происходит: Ошибка компиляции. JsonLoadFile возвращает void, а не загруженный объект или bool.
Правильное решение:
MyConfig cfg = new MyConfig(); // Сначала создать экземпляр со значениями по умолчанию
JsonFileLoader<MyConfig>.JsonLoadFile(path, cfg); // Заполняет cfg на месте
// cfg теперь содержит загруженные значения (или по-прежнему имеет значения по умолчанию, если файл был невалиден)Примечание: Более новый метод
JsonFileLoader<T>.LoadFile()возвращаетbool, ноJsonLoadFile(часто встречающаяся версия) --- нет.
16. Нет подстановки значений #define
Что вы бы написали:
#define MAX_PLAYERS 60
#define VERSION_STRING "1.0.0"
int max = MAX_PLAYERS;Что происходит: Ошибка компиляции. #define в Enforce Script создаёт только флаги существования для проверок #ifdef. Подстановка значений не поддерживается.
Правильное решение:
// Используйте const для значений
const int MAX_PLAYERS = 60;
const string VERSION_STRING = "1.0.0";
// Используйте #define только для флагов условной компиляции
#define MY_MOD_ENABLED17. Нет интерфейсов / абстрактных классов (принудительно)
Что вы бы написали:
interface ISerializable
{
void Serialize();
void Deserialize();
}
abstract class BaseProcessor
{
abstract void Process();
}Что происходит: Ключевые слова interface и abstract не существуют.
Правильное решение: Используйте обычные классы с пустыми базовыми методами:
// «Интерфейс» --- базовый класс с пустыми методами
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. Нет ограничений для дженериков
Что вы бы написали:
class Container<T> where T : EntityAI // Ограничить T до EntityAIЧто происходит: Ошибка компиляции. Конструкция where не существует. Параметры шаблона принимают любой тип.
Правильное решение: Валидируйте в рантайме:
class EntityContainer<Class T>
{
void Add(T item)
{
// Проверка типа в рантайме вместо ограничения на этапе компиляции
EntityAI eai;
if (!Class.CastTo(eai, item))
{
ErrorEx("EntityContainer only accepts EntityAI subclasses");
return;
}
// продолжить
}
}19. Нет валидации enum
Что вы бы написали:
EDamageState state = (EDamageState)999; // Ожидаете ошибку или исключениеЧто происходит: Без ошибки. Любое значение int может быть присвоено переменной enum, даже значения за пределами определённого диапазона.
Правильное решение: Валидируйте вручную:
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. Нет вариативных параметров
Что вы бы написали:
void Log(string format, params object[] args)
void Printf(string fmt, ...)Что происходит: Ошибка компиляции. Вариативные параметры не существуют.
Правильное решение: Используйте string.Format с фиксированным количеством параметров или классы Param:
// 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. Нет вложенных объявлений классов
Что вы бы написали:
class Outer
{
class Inner // Вложенный класс
{
int value;
}
}Что происходит: Ошибка компиляции. Классы не могут быть объявлены внутри других классов.
Правильное решение: Объявляйте все классы на верхнем уровне, используйте соглашения об именовании для отображения связей:
class MySystem_Config
{
int value;
}
class MySystem
{
ref MySystem_Config m_Config;
}22. Статические массивы фиксированного размера
Что вы бы написали:
int size = GetCount();
int arr[size]; // Динамический размер в рантаймеЧто происходит: Ошибка компиляции. Размеры статических массивов должны быть константами времени компиляции.
Правильное решение:
// Используйте const для статических массивов
const int BUFFER_SIZE = 64;
int arr[BUFFER_SIZE];
// Или используйте динамические массивы для задания размера в рантайме
array<int> arr = new array<int>;
arr.Resize(GetCount());23. array.Remove не сохраняет порядок
Что вы бы написали (ожидая сохранение порядка):
array<string> items = {"A", "B", "C", "D"};
items.Remove(1); // Ожидаете: {"A", "C", "D"}Что происходит: Remove(index) меняет элемент местами с последним элементом, затем удаляет последний. Результат: {"A", "D", "C"}. Порядок НЕ сохраняется.
Правильное решение:
// Используйте RemoveOrdered для сохранения порядка (медленнее --- сдвигает элементы)
items.RemoveOrdered(1); // {"A", "C", "D"} --- правильный порядок
// Используйте RemoveItem для поиска и удаления по значению (тоже упорядоченно)
items.RemoveItem("B"); // {"A", "C", "D"}24. Нет #include --- всё через config.cpp
Что вы бы написали:
#include "MyHelper.c"
#include "Utils/StringUtils.c"Что происходит: Нет эффекта или ошибка компиляции. Директива #include не существует.
Правильное решение: Все файлы скриптов загружаются через config.cpp в записи CfgMods мода. Порядок загрузки определяется слоем скриптов (3_Game, 4_World, 5_Mission) и алфавитным порядком внутри каждого слоя.
// 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. Нет пространств имён
Что вы бы написали:
namespace MyMod { class Config { } }
namespace MyMod.Utils { class StringHelper { } }Что происходит: Ошибка компиляции. Ключевое слово namespace не существует. Все классы разделяют единое глобальное пространство.
Правильное решение: Используйте префиксы имён для избежания конфликтов:
class MyConfig { } // MyFramework
class MyAI_Config { } // MyAI Mod
class MyM_MissionData { } // MyMissions Mod
class VPP_AdminConfig { } // VPP Admin26. Методы строк изменяют на месте
Что вы бы написали (ожидая возвращаемое значение):
string upper = myString.ToUpper(); // Ожидаете: возвращает новую строкуЧто происходит: ToUpper() и ToLower() изменяют строку на месте и возвращают void.
Правильное решение:
// Сначала сделайте копию, если нужно сохранить оригинал
string original = "Hello World";
string upper = original;
upper.ToUpper(); // upper теперь "HELLO WORLD", original не изменён
// То же самое для TrimInPlace
string trimmed = " hello ";
trimmed.TrimInPlace(); // "hello"27. Циклы ref вызывают утечки памяти
Что вы бы написали:
class Parent
{
ref Child m_Child;
}
class Child
{
ref Parent m_Parent; // Циклическая ссылка --- оба ссылаются друг на друга
}Что происходит: Ни один объект никогда не будет удалён сборщиком мусора. Счётчики ссылок никогда не достигнут нуля, потому что каждый хранит ref на другого.
Правильное решение: Одна сторона должна использовать сырой (не-ref) указатель:
class Parent
{
ref Child m_Child; // Родитель ВЛАДЕЕТ дочерним (ref)
}
class Child
{
Parent m_Parent; // Дочерний ССЫЛАЕТСЯ на родителя (сырой --- без ref)
}28. Нет гарантии деструктора при остановке сервера
Что вы бы написали (ожидая очистку):
void ~MyManager()
{
SaveData(); // Ожидаете, что это выполнится при завершении работы
}Что происходит: Завершение работы сервера может убить процесс до вызова деструкторов. Ваше сохранение не происходит.
Правильное решение: Сохраняйте проактивно через регулярные интервалы и при известных событиях жизненного цикла:
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++):
{
FileHandle f = OpenFile("test.txt", FileMode.WRITE);
// f автоматически закрывается при выходе из области видимости
}Что происходит: Enforce Script не закрывает файловые дескрипторы при выходе переменных из области видимости (даже с autoptr).
Правильное решение: Всегда закрывайте ресурсы явно:
FileHandle fh = OpenFile("$profile:MyMod/data.txt", FileMode.WRITE);
if (fh != 0)
{
FPrintln(fh, "data");
CloseFile(fh); // Нужно закрыть вручную!
}30. GetGame().GetPlayer() возвращает null на сервере
Что вы бы написали:
PlayerBase player = PlayerBase.Cast(GetGame().GetPlayer());
player.DoSomething(); // КРАШ на сервере!Что происходит: GetGame().GetPlayer() возвращает локального игрока. На выделенном сервере локального игрока нет --- возвращается null.
Правильное решение: На сервере перебирайте список игроков:
#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();
}
#endif31. Классы sealed нельзя расширять (1.28+)
Начиная с DayZ 1.28, компилятор Enforce Script принудительно применяет ключевое слово sealed. Класс или метод, помеченный sealed, не может быть унаследован или переопределён. Если вы попытаетесь расширить sealed-класс:
// Если BI пометил SomeVanillaClass как sealed:
class MyClass : SomeVanillaClass // ОШИБКА КОМПИЛЯЦИИ в 1.28+
{
}Проверяйте дамп ванильных скриптов на наличие классов, помеченных sealed, прежде чем пытаться наследовать от них. Если вам нужно изменить поведение sealed-класса, используйте композицию (оберните его) вместо наследования.
32. Ограничение параметров метода: максимум 16 (1.28+)
В Enforce Script всегда было ограничение в 16 параметров для методов, но до 1.28 это было тихим переполнением буфера, вызывающим случайные краши. Начиная с 1.28 компилятор выдает жёсткую ошибку:
// ОШИБКА КОМПИЛЯЦИИ в 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) дают неправильные результаты:
int val = 1;
if (val < int.MIN) // Вычисляется как TRUE --- должно быть false
{
// Этот блок выполняется некорректно
}Избегайте прямых сравнений с int.MIN. Используйте сохранённую константу или сравнивайте с конкретным отрицательным значением.
35. Логическое отрицание элемента массива не работает
Прямое логическое отрицание элементов массива не компилируется:
array<int> list = {0, 1, 2};
if (!list[1]) // НЕ КОМПИЛИРУЕТСЯ
if (list[1] == 0) // Работает --- используйте явное сравнениеВсегда используйте явные проверки на равенство при тестировании элементов массива на истинность.
36. Сложное выражение при присваивании в массив вызывает краш
Присваивание сложного выражения напрямую в элемент массива может вызвать segmentation fault:
// КРАШИТСЯ в рантайме
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 на второй итерации:
// КРАШИТСЯ на 2-м элементе
foreach (string item : GetMyArray())
{
}
// БЕЗОПАСНО --- сначала сохраните в локальную переменную
array<string> items = GetMyArray();
foreach (string item : items)
{
}38. Приоритет побитовых операторов и операторов сравнения
Побитовые операторы имеют более низкий приоритет, чем операторы сравнения, следуя правилам C/C++:
int flags = 5;
int mask = 4;
if (flags & mask == mask) // НЕПРАВИЛЬНО: вычисляется как flags & (mask == mask)
if ((flags & mask) == mask) // ПРАВИЛЬНО: всегда используйте скобки39. Пустые блоки #ifdef / #ifndef вызывают краш
Пустые блоки условной компиляции --- даже содержащие только комментарии --- вызывают segmentation fault:
#ifdef SOME_DEFINE
// Этот блок только с комментарием вызывает SEGFAULT
#endifВсегда включайте хотя бы один исполняемый оператор или полностью уберите блок.
40. GetGame().IsClient() возвращает false при загрузке
Во время фазы загрузки клиента GetGame().IsClient() возвращает false, а GetGame().IsServer() возвращает true --- даже на клиентах. Используйте IsDedicatedServer() вместо этого:
// НЕНАДЁЖНО во время фазы загрузки
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::vector | array<T> |
std::map | map<K,V> |
std::unique_ptr | ref / autoptr |
dynamic_cast<T*> | Class.CastTo() или T.Cast() |
try/catch | Защитные выражения |
operator+ | Именованные методы (Add()) |
namespace | Префиксы имён (My, VPP_) |
#include | config.cpp files[] |
| RAII | Ручная очистка в методах жизненного цикла |
| Множественное наследование | Одиночное наследование + композиция |
nullptr | null / NULL |
| Шаблоны с ограничениями | Шаблоны без ограничений + проверки в рантайме |
do...while | while (true) { ... if (!cond) break; } |
Переход с C#
| Возможность C# | Эквивалент в Enforce Script |
|---|---|
interface | Базовый класс с пустыми методами |
abstract | Базовый класс + ErrorEx в базовых методах |
delegate / event | ScriptInvoker |
Лямбда => | Именованные методы |
?. null conditional | Ручные проверки на null |
?? null coalescing | if (!x) x = default; |
try/catch | Защитные выражения |
using (IDisposable) | Ручная очистка |
Свойства { get; set; } | Публичные поля или явные геттеры/сеттеры |
| LINQ | Ручные циклы |
nameof() | Жёстко заданные строки |
async/await | CallLater / таймеры |
Переход с Java
| Возможность Java | Эквивалент в Enforce Script |
|---|---|
interface | Базовый класс с пустыми методами |
try/catch/finally | Защитные выражения |
| Сборка мусора | ref + подсчёт ссылок (нет GC для циклов) |
@Override | Ключевое слово override |
instanceof | obj.IsInherited(typename) |
package | Префиксы имён |
import | config.cpp files[] |
enum с методами | enum (только int) + вспомогательный класс |
final | const (только для переменных) |
| Аннотации | Не доступны |
Переход с Python
| Возможность Python | Эквивалент в Enforce Script |
|---|---|
| Динамическая типизация | Статическая типизация (все переменные типизированы) |
try/except | Защитные выражения |
lambda | Именованные методы |
| List comprehension | Ручные циклы |
**kwargs / *args | Фиксированные параметры |
| Утиная типизация | IsInherited() / Class.CastTo() |
__init__ | Конструктор (то же имя, что и класс) |
__del__ | Деструктор (~ClassName()) |
import | config.cpp files[] |
| Множественное наследование | Одиночное наследование + композиция |
None | null / 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 | Сначала сохраните в локальную переменную |
| Приоритет побитовых операторов и сравнения | Неправильное вычисление | Всегда используйте скобки |
Пустые блоки #ifdef | Segfault | Включите оператор или уберите блок |
IsClient() при загрузке | Возвращает false | Используйте IsDedicatedServer() |
| Ошибка компиляции в неправильном файле | Ложное расположение | Проверяйте файл, разобранный после указанного |
Файлы crash_*.log | Не настоящие краши | Это исключения скриптов времени выполнения |
Навигация
| Предыдущая | Вверх | Следующая |
|---|---|---|
| 1.11 Обработка ошибок | Часть 1: Enforce Script | 1.13 Функции и методы |
