Гра з кодом: ощадливий підхід (Українська (Ukrainian))

Гра з кодом: ощадливий підхід

Sunday, 23 November 2025

//

18 minute read

Для чого ставитися до своєї гілки як до п'ятниці і грати як до законної роботи означає краще програмне забезпечення з меншим стресом.

Гарячий вибір: Більшість розробників намагаються бути досконалими на кожному етапі розвитку, і це вбиває як їх творчість, так і продуктивність.

Вступ

Я будував програмне забезпечення професійно для... ну, давайте скажемо, що я пам'ятаю, коли "Агіле" був новою ідеєю, а не корпоративним гучномовцем, озброєним менеджерами проектів, щоб виправдати більше відставок.

Протягом років, я помітив дещо: найкращий код, який я коли-небудь писав, прийшов з проектів, де я відчував Дозволити відтворенняНайгірший код з'явився в проектах, де кожен удар за ключем відчував, що він ретельно переглядався, де я намагався написати код "продукт-готовий" з хвилини на хвилину.

Це не випадковість. Креативність і тиск погано змішуються.

Я розробив підхід, який відділяє безладне, творче дослідження від полірованого, готового до виробництва.Зробити це працює, зробити це красиво, заблокувати його" - але в дійсності, це тільки давати собі дозвіл експериментувати без ваги досконалости, яка висить над тобою.

Три фази

Давайте я поясню: це не те, щоб бути неохайним чи уникати дисципліни. буде вчасно застосовувати належне покарання.

graph LR
    subgraph "Phase 1: Make It Work"
        A[New Requirement] --> B[Explore & Experiment]
        B --> C{Does It Work?}
        C -->|No| D[Try Different Approach]
        D --> B
        C -->|Yes| E[Basic Solution]
    end

    subgraph "Phase 2: Make It Pretty"
        E --> F[Refactor & Clean]
        F --> G[Apply SOLID Pragmatically]
        G --> H[Get Stakeholder Feedback]
        H --> I[Polished Solution]
    end

    subgraph "Phase 3: Lock It Down"
        I --> J[Add Tests]
        J --> K[Security Review]
        K --> L[Add Monitoring]
        L --> M[Production Ready]
    end

    style A stroke:#f59e0b,stroke-width:3px
    style E stroke:#0ea5e9,stroke-width:3px
    style I stroke:#8b5cf6,stroke-width:3px
    style M stroke:#10b981,stroke-width:4px

Фаза 1.

Мантра: Грай першим. Підтвердь свої припущення.

Це фаза, де ваша гілка є пісочницею. Код Мессі в порядку. З мертвими кінцями все гаразд. Починаючи з трьох разів. ГараздВи не будуєте тут собор, ви малюєте серветку.

Що ви намагаєтеся з'ясувати:

  • Чи справді цей API поводиться так, як вважають документи? (Сп'юлер: це рідко трапляється)
  • Чи можу я взагалі вирішити цю проблему за допомогою інструментів, які у мене є?
  • Які ж справжні вимоги, а не ті, що записані в квитку?
  • Це двогодинна проблема чи двотижнева проблема?

Критичне розуміння: До того, как ты восстанавливаешь PR, твоя ветчина - это ваш...це ваша експериментальна лабораторія. Ніхто не повинен бачити, як починається ваш штучний код для усування вад, ваш код " TODO: це жахливо, виправити пізніше." Але перевірте у фільмі OFTEN every разу, коли ви маєте щось, що працює, крапає і штовхає. Гачок у ваш трубопровод CI; хороший трубопровід дасть вам більше інформації (дослідження ініціалізації, зміни в іншому місці тощо). Це FINE, щоб перевірити у потворному, ледь зшиті разом прямі коди. Це POINT. Навіть будучи ветераном трьох класів, я все ще роблю це.

Бічна вигода: Часті входи - це серцебиття. що робота йде вперед... ти маєш виконувати продуктивність у Slack.

Ця психологічна свобода є необхідною. Коли ви знаєте, що ви можете все відкинути і почати все спочатку, ви робите сміливіші рішення. Ви пробуєте цей дивний підхід, який ймовірно не спрацює. Іноді він працює чудово. Іноді вам потрібно прочитати статтю, подивитися відео на YouTube, подумати деякий час. Пам' ятайте: ваша робота полягає в тому, щоб донести добрі програми і гарні рішення, а не вгадувати їх швидко. Хороші менеджери розуміють, що ви - НЕ Є ТЕПИСТ.

// Phase 1 code looks like this - and THAT'S OKAY
public async Task<Result> ProcessThing(Request req)
{
    // TODO: what if this is null?
    var data = await _api.GetSomething(req.Id);

    // This probably isn't right but let's see what happens
    var transformed = data.Items
        .Where(x => x.Status != "deleted") // Is this the right filter?
        .Select(x => new Thing { Name = x.Name }); // Missing loads of fields

    // HACK: hardcoded for now
    return new Result { Items = transformed.ToList(), Total = 42 };
}

це код виробництва? чи є це корисним? доки ви вкладаєте час на полірування чогось, що не спрацює.

Примітка щодо зворотного зв' язку: Ви можете отримати зворотній зв'язок any Точка в 1/ 1, а клавіша - вибір хто Мета полягає в тому, щоб побудувати найкращу рису, а не слідувати жорсткому процесу.

Розмова може зберегти робочі дні. look замість того, чи це працюєЦе добре; вони дають відгук у фазі 2.

Іноді вам потрібні відгуки на прояснити"Обставна каже, що "помилки руки" означає "відтворити" три рази, або "попередити"?" - це розмова фази 1.

Якщо вхід заповнювача є критичним, але він бореться з жорстким кодом, створіть тривіальну демонстрацію. жорсткий кодований шлях до щасливого шляху, який показує концепцію, що вона може розблокувати цінний відгук без відволікання від пропущених випадків границь.

Будьте прагматичними.

Фаза 2.

Мантра: Тепер, коли ти знаєш, що ти будуєш, зроби це. добрий.

Ви підтвердили підхід, ви розумієте форму проблеми, і, мабуть, знайшли десяток випадків, коли оригінальний квиток не згадувався.

Тепер ти вичистиш його:

// Phase 2: Same logic, but actually maintainable
public async Task<ProcessingResult> ProcessItemsAsync(
    ProcessingRequest request,
    CancellationToken cancellationToken = default)
{
    ArgumentNullException.ThrowIfNull(request);

    var items = await _itemRepository.GetActiveItemsAsync(
        request.AccountId,
        cancellationToken);

    var processedItems = items
        .Where(item => item.Status != ItemStatus.Deleted)
        .Select(item => _mapper.ToProcessedItem(item))
        .ToList();

    return new ProcessingResult
    {
        Items = processedItems,
        TotalCount = processedItems.Count,
        ProcessedAt = _timeProvider.UtcNow
    };
}

Ось де ви:

  • Застосувати принципи SOLID прагматично (Знай їх, але не поклоняйся їм)
  • Напишіть речі на ім'я
  • Додати підказки типу і документацію XML
  • Думайте про поверхню API з перспективи виклику
  • Обробка справи, яку ви знайшли у фазі 1

Критично, це найкращий час для нетехнологічного зворотного зв'язку. Функціональна можливість тепер є видимою і придатною для використання, але ви ще не вклали для неї декілька годин запису тестів. Якщо замість цього вони кажуть " насправді, ми хотіли, щоб програма виконувала роль X ," ви можете обернути її без того, щоб програма вилучала комплекс тестів.

Фаза 3: Заблокувати

Мантра: Укріпи його, випробуй, зроби його стійким.

Тепер лише тепер ви можете написати ваші тести. додати перевірку безпеки. внесіть правильні помилки для справи виробу границь.

Зачем ждать сейчас? ти нарешті знаєш, що ти перевіряєш.

Тести, записані у фазі 1, мали б бути неправильними, але ви ще не зрозуміли проблеми. Тести, записані у фазі 2, мали б бути записані у фазі 2, ви все ще покращували API. Тести, записані у фазі 3 є правильнуТому що реалізація стабільна.

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(100)]
public async Task ProcessItemsAsync_WithValidRequest_ReturnsCorrectCount(
    int itemCount)
{
    // Arrange
    var items = _fixture.CreateMany<Item>(itemCount).ToList();
    _mockRepository
        .Setup(r => r.GetActiveItemsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(items);

    // Act
    var result = await _sut.ProcessItemsAsync(
        new ProcessingRequest { AccountId = Guid.NewGuid() });

    // Assert
    result.TotalCount.Should().Be(itemCount);
    result.Items.Should().HaveCount(itemCount);
}

[Fact]
public async Task ProcessItemsAsync_WithNullRequest_ThrowsArgumentNullException()
{
    // Act & Assert
    await _sut.Invoking(s => s.ProcessItemsAsync(null!))
        .Should().ThrowAsync<ArgumentNullException>();
}

[Fact]
public async Task ProcessItemsAsync_ExcludesDeletedItems()
{
    // Arrange
    var items = new[]
    {
        new Item { Status = ItemStatus.Active },
        new Item { Status = ItemStatus.Deleted },
        new Item { Status = ItemStatus.Active }
    };
    _mockRepository
        .Setup(r => r.GetActiveItemsAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(items);

    // Act
    var result = await _sut.ProcessItemsAsync(
        new ProcessingRequest { AccountId = Guid.NewGuid() });

    // Assert
    result.Items.Should().HaveCount(2);
}

Ця фаза також включає:

  • Рецензування безпеки (аутентифікація, авторизація, перевірка вхідних даних)
  • Короткі міркування (що відбувається під навантаженням?)
  • Аналіз режиму помилок (а що, якщо база даних повільна? Що робити, якщо API вичерпається?)
  • Моніторування (як ми дізнаємося чи це розриває виробництво?)

Кореографія зворотного зв' язку

Разные люди должны быть замешаны на разных стадиях.

graph TB
    subgraph "Phase 1: Technical Voices"
        P1[Make It Work] --> T1[Senior Dev Review]
        T1 --> T2["Does the approach<br/>make sense?"]
        T2 --> T3["Any obvious<br/>architectural issues?"]
    end

    subgraph "Phase 2: Non-Technical Voices"
        P2[Make It Pretty] --> N1[Product Owner Demo]
        N1 --> N2["Does it meet<br/>the requirement?"]
        N2 --> N3["Is the UX<br/>intuitive?"]
    end

    subgraph "Phase 3: Ops/Security Voices"
        P3[Lock It Down] --> O1[Security Review]
        O1 --> O2[Operations Review]
        O2 --> O3["Will it hold up<br/>in production?"]
    end

    T3 --> P2
    N3 --> P3

    style T1 stroke:#3b82f6,stroke-width:2px
    style N1 stroke:#8b5cf6,stroke-width:2px
    style O1 stroke:#ef4444,stroke-width:2px

Фаза 1: Технічні голоси мають лише лише sensenior dev, архітектори. Вони можуть бачити крізь безлад у формі розв' язку. Нетехнологічні заповнювачі будуть панікувати через грубий код і питати про кольори кнопок, коли ви все ще знаходите модель даних.

Фаза 2: Нетехнічні голоси вітають. ви ще не написали тести.

Фаза 3: "А що, якщо це отримує 10х трафік?" - "А що, якщо хтось приймає злісні вхідні дані?" - ці питання стосуються лише одного разу, коли функція насправді працює.

Гілки як пісочниці

Ось що змінило моє життя в розробці: поки ти не піднімеш PR, твоя гілка повністю приватна.

Це може здатися очевидним, але я бачу, як розробники розглядають кожне зобов'язання так, ніби це відбувається у їх постійній пам'яті, вони бояться експериментувати, бо бояться писати потворний код, бо бояться щось зламати.

Я хочу, щоб ви подумали про цю унікальну гілочку як про дитячий майданчик, лабораторію, де очікують вибухів, і ніхто не постраждає.

graph TD
    A[Feature Branch Created] --> B[Experiment Freely]
    B --> C{Did it work?}
    C -->|No| D[Try Something Else]
    D --> B
    C -->|Yes| E[Clean Up the Mess]
    E --> F[Interactive Rebase]
    F --> G[Squash to Clean Commits]
    G --> H[Raise PR]
    H --> I[Public Code Review]

    style B stroke:#f59e0b,stroke-width:3px
    style D stroke:#f59e0b,stroke-width:2px
    style H stroke:#10b981,stroke-width:3px

    subgraph "Private - Go Mental"
        B
        C
        D
        E
    end

    subgraph "Public - Be Professional"
        H
        I
    end

На практиці це означає:

  • Передавати часто, навіть якщо код не збирається
  • Запис FIXME і TODO всюди коментарі
  • Залишати код зневаджування на місці, поки ви досліджуєте
  • Множина " спробувати цей підхід " внесе
  • Не хвилюйтеся про доставку повідомлень (позже ви розчавите)

Тоді, перед тим, як підняти PR:

  1. Очищення коду (Pase 2)
  2. Додати тести (Pase 3)
  3. Інтерактивна реbase, щоб розчавити всю цю заплутану історію
  4. Записати правильне повідомлення про внеску

PR - рецензенти бачать чистий, професійний код з чітким описом, вони не бачать, як починаються шість хибних чисел, 3aм "чому ця клята робота" не вступає в дію, або "не розблоковує" історію.

Очищена робота публічна.

Чому це зменшує стрес

Традиційний розвиток " завжди будьте професіоналами" є критичним. Кожна лінія здається постійною. Кожне рішення відчуває себе послідовним. Цей підхід усуває той тиск, що містить хаос: гра є законною роботою у фазі 1, зворотній зв'язок приходить, коли поворот є дешевим у фазі 2 і тести записані один раз правильно у фазі 3 тому що ви нарешті знаєте, що ви перевіряєте.

"Флейт - законна робота."

TDD працює чудово, якщо домен добре зрозумілий або ви маєте справу з відомою вадою. Але, якщо проблема ще не розв' язана, тести запису спочатку часто означають перевірку неправильної речі три рази у рядку. Такий підхід крок за кроком: з' ясуйте спочатку, тестуйте, якщо стабільно.

Pragmatic SOLID (І " Таємниця DRY ")

Отже, що цей трифазний набір думок робить з вашими принципами дизайну? Коротко кажучи: він вбиває догми.

Я згадував застосування принципів SOLID у фазі 2.

SOLID - це хороший набір принципів, я вчу їх, я використовую їх, але я бачив, як команди зникають у абстракційних дірках кролика в ім'я однієї відповідальності або Open/Closed, створюючи системи, такі гнучкі, що їх неможливо зрозуміти.

Ось моя евристика:

Застосувати SOLID, якщо:

  • Вам потрібні докази гнучкості.
  • Абстракція робить код зрозумілішим
  • Ви будуєте те, що інші будуть використовувати

Не застосовуйте SOLID, коли:

  • Ви робите припущення щодо майбутніх вимог.
  • Абстракція додає складність без прозорості
  • Ви створюєте внутрішній код, який тільки ви підтримуєте.

Три подібні рядки коду є кращими за передчасну абстракцію. Ви завжди можете видобути їх пізніше, якщо побачите їх чітко. Ви не зможете легко скасувати абстракцію, яку було випалено у вашу архітектуру.

// Over-engineered SOLID worship
public interface IThingProcessor { }
public interface IThingProcessorFactory { }
public class ThingProcessorFactory : IThingProcessorFactory { }
public class ThingProcessor : IThingProcessor { }
public class ThingProcessorDecorator : IThingProcessor { }
public class ThingProcessorValidationDecorator : IThingProcessor { }

// What you probably actually need
public class ThingProcessor
{
    public async Task<Result> ProcessAsync(Thing thing)
    {
        // Just do the thing
    }
}

Якщо вам потрібна гнучкість пізніше, реорганізація - дешева. Переобладнання на передньому плані дорого коштує, тому що ви підтримуєте абстракційні шари, які не заробляють на їх зберігання.

Прірва

І поки ми вбиваємо священних корів, давайте поговоримо про DRY Це, можливо, найбільш догматичний принцип в нашій індустрії, і якщо його застосовувати без думки, це завдає більше шкоди, ніж добра.

Проблема полягає в наступному: не всі дроблення є одним і тим самим типом множення..

Коли два фрагменти коду виглядають однаково, але слугують різним цілям, збираючи їх у спільні пари абстракцій, які повинні розвиватися незалежно. Тепер, коли один випадок використання має змінитися, ви або:

  1. Додавання параметрів і умов для обробки обох випадків (це погіршить абстракцію)
  2. Поламати інший випадок використання
  3. Копіювання спільного коду назад і його редагування (у цьому випадку копіювання DRY було неправильним)
// "DRY" solution - looks clever, causes pain
public async Task<Result> ProcessEntity<T>(
    T entity,
    bool validateFirst = true,
    bool sendNotification = false,
    Func<T, bool>? customFilter = null,
    Action<T>? preProcess = null,
    Action<T>? postProcess = null) where T : IEntity
{
    // 50 lines of conditional spaghetti trying to handle
    // "processing" for Users, Orders, and Products
    // because they all had 3 similar lines once
}

// What you probably actually need
public async Task<Result> ProcessUser(User user) { /* 15 clear lines */ }
public async Task<Result> ProcessOrder(Order order) { /* 15 clear lines */ }
public async Task<Result> ProcessProduct(Product product) { /* 15 clear lines */ }

Так, у другому підході є декілька "дубляцій," але кожен метод такий:

  • Легко зрозуміти в ізоляції
  • Легка зміна без побічних ефектів
  • Легка дія для вилучення під час вилучення цього типу сутностей
  • Простий для тестування з чіткими входами і виводами

Версія DRY - це кошмар, який слід підтримувати, оскільки вона намагається бути всім для всіх телефонів. Кожна зміна вимагає розуміння всіх випадків використання. Будь- яка помилка виправить ризики.

Моє правило: Розподіл множення, поки ви не побачите те саме річ три рази і ви впевнені, що вона являє собою те саме концепція, яка розвиватиметься разом. це доречна обережність.

Dupplication є набагато дешевшим за неправильну абстракцію. Ви завжди можете дедуплікувати пізніше, якщо шаблон буде зрозумілим. Ви не зможете легко скасувати погану абстракцію, яка з' єднана за допомогою бази даних коду.

Проблема інтерфейсу у C#

Ось ще одна священна корова, яку треба вбити. шаблон " Кожен клас потребує інтерфейсу ".

Ще у ранні дні ін'єкції залежності від NET хтось вирішив, що для перевірки коду вам слід вставляти інтерфейси всюди. Логіка така: ви не можете висміювати конкретний клас, отже, всі потреби кожної служби IService і ServiceImpl. Раптом кожна база коду C# була захаращена одноімпанельними інтерфейсами, які існували виключно для тестування.

// The interface tax - early 2010s style
public interface IUserService { }
public interface IOrderService { }
public interface IEmailService { }
public interface INotificationService { }
public interface IPaymentProcessor { }
public interface IInventoryManager { }

// Each with exactly ONE implementation
public class UserService : IUserService { }
public class OrderService : IOrderService { }
// ... you get the idea

Ми платили складний податок. Теоретично можливість перевірки і Теоретично Майбутня гнучкість, яка рідко була матеріалізована.

Справа в тому, що тобі це більше не потрібно.

Сучасні оболонки для насмішок, такі як NSubsitte і Moq може імітувати бетонні класи (у віртуальних методах). Важливішим є те, що контейнер ASP.NET працює ідеально з бетонними типами:

// Modern approach - just register the concrete type
services.AddScoped<UserService>();
services.AddScoped<OrderService>();
services.AddScoped<EmailService>();

// In your controller or service
public class OrderController(OrderService orderService, UserService userService)
{
    // Primary constructor injection - clean and simple
}

Немає інтерфейсів. Ні IOrderServiceЛише необхідні послуги, безпосередньо впорскуються.

"А як щодо тестування?" Я чую, як ти плачеш.

Для перевірки модулів ви можете скористатися такими варіантами:

  1. Робити способи ключів virtual і використовувати шаблон для насмішок
  2. Використовувати справжню службу з тестовою базою даних (у будь- якому разі тести на інтеграцію є ціннішими)
  3. Додати інтерфейс коли ви насправді потребуєте його для тестування

Третя точка має вирішальне значення: Перекодування інструментів надзвичайно добре працює у доставленні інтерфейсів.

На Їздцях або Visual Studio, це буквально Ctrl+. → " Інтерфейс витягання " - все зроблено. Кожен метод видобуто, клас оновлюється, щоб реалізувати його, і ви можете знайти або замінити всі використання за потреби. Це займає секунди.

// Phase 1 & 2: Just write the class
public class OrderService
{
    public async Task<Order> CreateOrderAsync(CreateOrderRequest request) { ... }
    public async Task<Order?> GetOrderAsync(int orderId) { ... }
    public async Task CancelOrderAsync(int orderId) { ... }
}

// Phase 3: Need to mock it for testing? Extract interface in 2 seconds
public interface IOrderService
{
    Task<Order> CreateOrderAsync(CreateOrderRequest request);
    Task<Order?> GetOrderAsync(int orderId);
    Task CancelOrderAsync(int orderId);
}

public class OrderService : IOrderService { ... }

Мій робочий процес зараз: interfaces можна знайти у фазі 3, а не фазі 1.

Під час " make it work " і " make it pretty ," я просто записую класи. Без передчасної абстракції. Жодного податку на інтерфейс на будь- який тип. Код простіший, простіший для навігації (без перестрибування між IFoo і Foo, і швидше писати.

Коли я "заблокую" і маю написати тести, потім Зазвичай, це менше, ніж ви думаєте, просто зовнішні інтеграції, такі як HTTP клієнти, бази даних і черги повідомлень.

Для служби внутрішнього бізнесу? а не глузувати з того, що я think так має бути.

// Services I typically DO extract interfaces for (external boundaries)
public interface IPaymentGateway { }      // Third-party API
public interface IEmailSender { }         // External service
public interface IBlobStorage { }         // Cloud storage

// Services I typically DON'T need interfaces for (internal logic)
public class OrderValidator { }           // Pure logic, test directly
public class PriceCalculator { }          // Pure logic, test directly
public class OrderService { }             // Test with real repo + in-memory DB

Суть у тому: Зупинити запис інтерфейсів " точно у випадку ." Запишіть бетонні класи. Якщо вам потрібен інтерфейс для тестування або справжнього поліморфізму пізніше, видобування одного з них займає буквально декілька секунд за допомогою сучасних інструментів.

Ваша база коду буде простішою, більш досяжною, і у вас не буде тягару обслуговування інтерфейсів під час синхронізації з впровадженням без жодної користі.

Агресивне з'єднання

Іронічно, цей підхід більше співвідноситься з оригінал Показ Agile ніж більшість процесів " Agile " з якими я зустрічався. Робота з програмним забезпеченням пришвидшена (Pazase 1). Відповідь на зміни (Pases 1- 2). Звичайна співпраця, якщо цю можливість можна демонтувати (Pase 2). Немає точок бігу, ніякого стеження за швидкістю, ніякого карт згорання.

Сучасне "Агіле" було захоплене ентузіастами процесів, які додали так багато церемоній, що немає часу на дослідження.

Коли НЕ користуватись цим підходом

Чесно кажучи, це не завжди правильний підхід.

Виправлення вад: Під час виправлення відомої вади стиль TDD має більше сенсу. Напишіть тест, за допомогою якого можна відтворити ваду, виправити її.

Проблеми з недотриманням: Якщо ви впроваджуєте стандартний алгоритм або шаблон, який ви використовували сто разів, вам не потрібна фаза дослідження.

Часові рамки з відомими вимогами: Якщо ви дійсно знаєте, що саме ви будуєте і коли потрібно до корабля, ви можете пропустити дослідження фази 1.

Високорегульовані середовища: Якщо для кожного з внесків потрібна підписка, пісочниця буде складнішою. Хоча ви можете робити дослідження локально, перш ніж натискати.

Але для більша applications governic seconds are a little balled, технічний підхід не є очевидним, і вам потрібен простір, щоб думати, що це підхід працює чудово.

Примітка для розробників Джуніора

Якщо ви на початку своєї кар'єри, ви можете відчувати тиск, щоб показати "досконалий" код весь час. Ви не мусите цього робити. Використовуйте цей підхід з трьома порціями, просто будьте ясними про те, в якій фазі ви перебуваєте, і завжди закінчуйте завдання аж до фази 3. Ніхто не буде звинувачувати вас за безладне дослідження коду, якщо він веде до відшліфованого, випробуваного, готового до виробництва коду в кінці.

Нижній рядок

Зроби це, зроби це красиво, заблокуй його.

Почніть з простого. Розробка з передбаченням, але не з припущеннями. Продумайте швидко. Додайте складність лише тоді, коли вона окупиться.

"Дивлення набагато дешевше, ніж неправильна абстракція."

Ваша гілка - це ваша пісочниця, аж поки ви не зберете PR. Технічний зворотній зв'язок наступає на ранній стадії, нетехнічне зворотній зв'язок відбувається посередині, з'єднання по опустелювання і зворотній зв'язок з безпекою.

Це зберігає творчість живою, зменшує стрес і виробляє системи, які масштабуються елегантно, тому що ви зрозуміли проблему перед тим, як віддатися розв'язанню.

Перестаньте намагатися бути ідеальними з першої хвилини. Надайте собі дозвіл грати.


Пов' язане читання

Якщо це відкликає вас, вам також можуть сподобатися пов' язані з цим дописи:

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.