Простыми словами для начинающих Unity-разработчиков учим принципы KISS, DRY, YAGNI и BDUF через аналогии из жизни
Привет!
Сегодня мы изучим четыре правила, которые помогут тебе писать код так, чтобы он был понятным, красивым и работал без проблем. Эти правила называются KISS, DRY, YAGNI и BDUF.
KISS - "Делай Проще, глупыш!"
Что это такое?
KISS расшифровывается как "Keep It Simple, Stupid" - "Делай проще, глупыш!". Звучит грубо, но на самом деле это очень добрый совет. Он означает: "Не усложняй то, что можно сделать просто".
Пример из жизни
Представь, что ты хочешь сделать бутерброд. Можно взять хлеб, масло и колбасу - и всё готово! А можно начать строить сложную конструкцию из 15 ингредиентов, которая в итоге развалится у тебя в руках. KISS говорит: "Делай бутерброд простым!"
Плохой пример кода (сложный)
using UnityEngine;
public class ComplexPlayerController : MonoBehaviour
{
public float speed = 5f;
void Update()
{
// Получаем ввод игрока
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// Создаем сложную систему проверок
if (horizontal != 0 || vertical != 0)
{
// Создаем вектор движения
Vector3 movement = new Vector3(horizontal, 0, vertical);
// Нормализуем вектор для правильной скорости по диагонали
if (movement.magnitude > 1)
{
movement = movement.normalized;
}
// Применяем сложную формулу с проверками
if (movement.x > 0)
{
transform.position += new Vector3(movement.x * speed * Time.deltaTime, 0, 0);
}
else if (movement.x < 0)
{
transform.position += new Vector3(movement.x * speed * Time.deltaTime, 0, 0);
}
if (movement.z > 0)
{
transform.position += new Vector3(0, 0, movement.z * speed * Time.deltaTime);
}
else if (movement.z < 0)
{
transform.position += new Vector3(0, 0, movement.z * speed * Time.deltaTime);
}
}
}
}
Что происходит в плохом коде:
Мы делаем много лишних проверок и сложных вычислений. Код работает, но его трудно читать и понимать. Мы отдельно проверяем движение по X и Z, хотя можно сделать это одной строчкой.
Хороший пример кода (простой)
using UnityEngine;
public class SimplePlayerController : MonoBehaviour
{
public float speed = 5f; // Скорость движения игрока
void Update()
{
// Получаем ввод от игрока (стрелки или WASD)
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
// Создаем вектор движения
Vector3 movement = new Vector3(horizontal, 0, vertical);
// Двигаем игрока одной простой командой
transform.Translate(movement * speed * Time.deltaTime);
}
}
Что происходит в хорошем коде:
Мы делаем то же самое, но намного проще! Получили ввод, создали вектор движения, переместили игрока. Всё понятно с первого взгляда.
DRY - "Не Повторяйся!"
Что это такое?
DRY означает "Don't Repeat Yourself" - "Не повторяйся". Это правило говорит: "Если ты пишешь один и тот же код несколько раз, значит, ты делаешь что-то не так".
Пример из жизни
Представь, что ты каждый день идешь в школу одной и той же дорогой. Вместо того чтобы каждый раз заново думать "направо, налево, прямо", ты просто запомнил маршрут. В программировании тоже можно "запомнить" часто используемые действия.
Плохой пример кода (с повторениями)
using UnityEngine;
public class BadInventoryManager : MonoBehaviour
{
public int swordCount = 0;
public int shieldCount = 0;
public int potionCount = 0;
public void AddSword()
{
// Добавляем меч в инвентарь
swordCount++;
Debug.Log("Меч добавлен! Количество мечей: " + swordCount);
// Проверяем, не превышает ли количество максимум
if (swordCount > 10)
{
swordCount = 10;
Debug.Log("Инвентарь полон! Нельзя добавить больше мечей.");
}
}
public void AddShield()
{
// Добавляем щит в инвентарь
shieldCount++;
Debug.Log("Щит добавлен! Количество щитов: " + shieldCount);
// Проверяем, не превышает ли количество максимум
if (shieldCount > 10)
{
shieldCount = 10;
Debug.Log("Инвентарь полон! Нельзя добавить больше щитов.");
}
}
public void AddPotion()
{
// Добавляем зелье в инвентарь
potionCount++;
Debug.Log("Зелье добавлено! Количество зелий: " + potionCount);
// Проверяем, не превышает ли количество максимум
if (potionCount > 10)
{
potionCount = 10;
Debug.Log("Инвентарь полон! Нельзя добавить больше зелий.");
}
}
}
Что плохого в этом коде:
Мы три раза пишем почти одинаковый код! Единственное отличие - название предмета. Если нам понадобится изменить максимальное количество предметов с 10 на 20, придется менять код в трех местах.
Хороший пример кода (без повторений)
using UnityEngine;
public class GoodInventoryManager : MonoBehaviour
{
public int swordCount = 0;
public int shieldCount = 0;
public int potionCount = 0;
public int maxItems = 10; // Максимальное количество любого предмета
// Универсальная функция для добавления любого предмета
private void AddItem(ref int itemCount, string itemName)
{
// Увеличиваем количество предмета
itemCount++;
Debug.Log(itemName + " добавлен! Количество: " + itemCount);
// Проверяем максимум один раз для всех предметов
if (itemCount > maxItems)
{
itemCount = maxItems;
Debug.Log("Инвентарь полон! Нельзя добавить больше " + itemName.ToLower() + ".");
}
}
// Простые функции, которые используют универсальную логику
public void AddSword()
{
AddItem(ref swordCount, "Меч");
}
public void AddShield()
{
AddItem(ref shieldCount, "Щит");
}
public void AddPotion()
{
AddItem(ref potionCount, "Зелье");
}
}
Что хорошего в этом коде:
Мы написали логику добавления предмета только один раз в функции AddItem
. Теперь, если нужно что-то изменить (например, максимальное количество), мы меняем код только в одном месте!
YAGNI - "Тебе Это Не Понадобится!"
Что это такое?
YAGNI означает "You Aren't Gonna Need It" - "Тебе это не понадобится". Это правило говорит: "Не добавляй в код то, что может понадобиться в будущем. Добавляй только то, что нужно прямо сейчас".
Пример из жизни
Представь, что ты собираешь рюкзак в школу. По принципу YAGNI ты берешь учебники, ручки и тетради - то, что точно понадобится сегодня. А не берешь палатку, удочку и коньки "на всякий случай".
Плохой пример кода (лишние функции)
using UnityEngine;
public class OverEngineeredPlayer : MonoBehaviour
{
public float health = 100f;
public float mana = 50f;
public int level = 1;
public string playerName = "Герой";
// Эта функция нужна для игры
public void TakeDamage(float damage)
{
health -= damage;
Debug.Log("Игрок получил урон: " + damage);
}
// Все функции ниже написаны "на будущее", но в игре не используются!
public void LevelUp()
{
// Функция повышения уровня (пока не нужна)
level++;
Debug.Log("Уровень повышен до: " + level);
}
public void CastSpell(string spellName)
{
// Система магии (пока не нужна)
Debug.Log("Заклинание " + spellName + " произнесено!");
}
public void SaveToFile()
{
// Сохранение в файл (пока не нужно)
Debug.Log("Игра сохранена!");
}
public void ConnectToDatabase()
{
// Подключение к базе данных (пока не нужно)
Debug.Log("Подключение к базе данных...");
}
public void EnableVRMode()
{
// Режим виртуальной реальности (пока не нужен)
Debug.Log("VR режим включен!");
}
}
Что плохого в этом коде:
Мы написали кучу функций, которые в игре не используются! Это усложняет код и тратит наше время. А еще эти функции могут содержать ошибки, о которых мы даже не узнаем.
Хороший пример кода (только нужное)
using UnityEngine;
public class SimplePlayer : MonoBehaviour
{
public float health = 100f; // Здоровье игрока
// Единственная функция, которая действительно нужна в игре сейчас
public void TakeDamage(float damage)
{
// Уменьшаем здоровье на количество полученного урона
health -= damage;
Debug.Log("Игрок получил урон: " + damage + ". Осталось здоровья: " + health);
// Проверяем, не умер ли игрок
if (health <= 0)
{
Debug.Log("Игрок погиб!");
}
}
}
Что хорошего в этом коде:
Мы добавили только то, что действительно нужно игре прямо сейчас. Когда понадобятся магия, сохранения или иное - мы добавим их позже. А пока код простой и понятный.
BDUF - "Не Планируй Слишком Много Заранее!"
Что это такое?
BDUF означает "Big Design Up Front" - "Большое планирование заранее". Это антипаттерн (то есть плохая практика), которую нужно избегать. BDUF означает ситуацию, когда программист тратит очень много времени на планирование всех деталей проекта, вместо того чтобы начать делать и учиться в процессе.
Важно понимать: планирование нужно! Дорожная карта, техническое задание, основные цели - всё это важно. Проблема BDUF в том, что люди тратят месяцы на планирование каждой мелочи, но так и не начинают делать. Правильно: спланировать основу и начать делать, корректируя план по ходу.
Пример из жизни
Представь, что ты хочешь научиться рисовать. Правильное планирование: решить что рисовать (пейзаж), подготовить базовые материалы (карандаш, бумага), наметить план (сначала контур, потом детали). BDUF подход: потратить месяц на изучение всех видов красок, кистей, техник и теории цвета, но так и не нарисовать ни одной картинки. Лучше начать с простого изучая новое по мере необходимости.
Плохой пример (BDUF подход)
using UnityEngine;
using System.Collections.Generic;
// Попытка сразу создать "идеальную" сложную систему
public class OverDesignedGameManager : MonoBehaviour
{
// Сложная система для всех возможных типов игроков
public enum PlayerType { Human, AI, NetworkPlayer, GuestPlayer, AdminPlayer }
public enum GameMode { SinglePlayer, MultiPlayer, Coop, PvP, Tournament, Training }
public enum DifficultyLevel { VeryEasy, Easy, Normal, Hard, VeryHard, Impossible }
// Класс для хранения всех возможных настроек
[System.Serializable]
public class GameSettings
{
public PlayerType playerType;
public GameMode gameMode;
public DifficultyLevel difficulty;
public bool enableSound;
public bool enableMusic;
public bool enableParticles;
public bool enableShadows;
public int maxPlayers;
public float gameTime;
public List<string> availableLevels;
}
public GameSettings settings; // Настройки игры
public Dictionary<int, PlayerData> allPlayers; // Данные всех игроков
// Сложная функция инициализации, которая пытается предусмотреть все случаи
void Start()
{
// Инициализируем сложную систему
InitializeComplexSystem();
}
void InitializeComplexSystem()
{
// Много кода для обработки всех возможных вариантов
// (большая часть из которых в простой игре не понадобится)
Debug.Log("Инициализация сложной системы...");
}
}
// Сложный класс для данных игрока со множеством полей "на будущее"
[System.Serializable]
public class PlayerData
{
public string name;
public int level;
public int experience;
public float health;
public float mana;
public int gold;
public List<string> inventory;
public List<string> achievements;
public Vector3 lastPosition;
public string guildName;
public int reputation;
// И еще много полей, которые пока не нужны...
}
Что плохого в этом подходе:
Мы пытаемся сразу создать систему, которая будет работать для любой игры. Но в итоге получается сложный код, который трудно понять и изменить. А большая часть функций нам пока не нужна!
Хороший пример (простое начало)
using UnityEngine;
// Простая система, которая делает только то, что нужно прямо сейчас
public class SimpleGameManager : MonoBehaviour
{
public bool gameStarted = false; // Началась ли игра
public float gameTime = 0f; // Время игры
void Start()
{
// Просто запускаем игру
StartGame();
}
void Update()
{
// Если игра началась, считаем время
if (gameStarted)
{
gameTime += Time.deltaTime;
}
}
public void StartGame()
{
// Запускаем игру простой командой
gameStarted = true;
Debug.Log("Игра началась!");
}
public void EndGame()
{
// Заканчиваем игру
gameStarted = false;
Debug.Log("Игра окончена! Время игры: " + gameTime + " секунд");
}
}
Что хорошего в этом подходе:
Мы создали простую систему, которая работает и делает то, что нужно сейчас. Когда игра станет сложнее, мы добавим новые функции. Но начинаем с малого!
Вот пример как применять все принципы вместе:
Реализация с учетом всех принципов
using UnityEngine;
// Простой игрок, следующий всем принципам
public class SmartPlayer : MonoBehaviour
{
[Header("Настройки игрока")]
public float speed = 5f; // Скорость движения
public float health = 100f; // Здоровье игрока
void Update()
{
// KISS: простое движение одной строчкой
MovePlayer();
// Проверяем здоровье
CheckHealth();
}
void MovePlayer()
{
// KISS: получаем ввод и двигаемся просто
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Vector3 movement = new Vector3(horizontal, 0, vertical);
transform.Translate(movement * speed * Time.deltaTime);
}
void CheckHealth()
{
// YAGNI: проверяем только то, что нужно сейчас
if (health <= 0)
{
Debug.Log("Игрок погиб!");
}
}
// DRY: универсальная функция для любого урона
public void TakeDamage(float damage)
{
health -= damage;
Debug.Log("Получен урон: " + damage + ". Здоровье: " + health);
}
// DRY: универсальная функция для любого лечения
public void Heal(float healAmount)
{
health += healAmount;
Debug.Log("Восстановлено здоровья: " + healAmount + ". Здоровье: " + health);
}
}
Что произошло в коде:
KISS - код простой и понятный, никаких сложных конструкций
DRY - функции
TakeDamage
иHeal
можно использовать для любых ситуацийYAGNI - добавили только то, что нужно для работы игрока сейчас
Избегаем BDUF - не планируем сложную систему заранее, начинаем с простого
Заключение
Эти четыре принципа - необходимы для программиста:
KISS - "Делай проще!" Не усложняй то, что можно сделать просто.
DRY - "Не повторяйся!" Если пишешь одинаковый код дважды, вынеси его в отдельную функцию.
YAGNI - "Не добавляй лишнее!" Пиши только то, что нужно прямо сейчас.
Избегай BDUF - "Не планируй всё заранее!" Начинай с простого и развивай постепенно.
Важное уточнение:
Планирование НУЖНО и ВАЖНО. Дорожная карта, ТЗ, основные цели - это правильно
Следуя этим правилам, твой код будет красивым, понятным и надежным. А главное - ты будешь получать удовольствие от программирования, потому что всё будет работать как надо!
Помни: лучший код - это не самый сложный код, а код, который решает задачу эффективно и понятно.
Важные предупреждения для разумного применения
Не превращай принципы в догму
Эти принципы - инструменты, а не религия. Их цель - помочь тебе писать лучший код, а не усложнить жизнь. Если следование принципу делает код хуже - не следуй ему слепо.
Примеры разумного нарушения:
Иногда небольшое повторение кода (нарушение DRY) лучше, чем сложная абстракция
Простая система может не нуждаться в архитектурных излишествах
В прототипе можно временно "нарушить" YAGNI ради быстрой проверки идеи
Опыт важнее правил
Начинающему разработчику сложно понять, когда применять принципы, а когда - нет. Это нормально! Начни с простого:
Сначала - заставь код работать
Потом - сделай его понятным
Затем - применяй принципы для улучшения
Никогда - не усложняй ради соблюдения принципов
Контекст решает всё
То, что работает в большом проекте с командой из 20 человек, может быть излишним для учебного проекта. Масштаб имеет значение:
Для инди-проекта многие принципы излишни. Но если в проекте работает более 3-5 человек, то уже стоит применять некоторые принципы, иначе потом возникнут проблемы с конфликтами кода при мерже (merge conflicts) и командной разработкой.
gybson_63
Про BDUF было очень внезапно!
Существует только "Сначала". Напиши очень мало кода, но до конца. Иначе ты будешь писать сразу техдолг.