# Παίζοντας με τον κώδικα: Μια αποτελεσματική προσέγγιση αγάπη

<datetime class="hidden">2025-11-23T10:00</datetime>

<!-- category -- Software Development, Agile, Best Practices, Craftsmanship -->
**Γιατί αντιμετωπίζετε το υποκατάστημα σας ως ένα sandboxκαι να παίξετε ως νόμιμη εργασία σας παράγει καλύτερο λογισμικό με λιγότερη ένταση.**

> **Hot take:** Οι περισσότεροι προγραμματιστές προσπαθούν να είναι τέλειοι σε κάθε στάδιο της ανάπτυξης, και σκοτώνει τόσο τη δημιουργικότητά τους όσο και την παραγωγικότητά τους.

## Εισαγωγή

Έχω χτίσει λογισμικό επαγγελματικά για... Λοιπόν, ας πούμε ότι θυμάμαι όταν "Agile" ήταν μια νέα ιδέα και όχι ένα εταιρικό buzzword οπλισμένο από τους διαχειριστές του έργου για να δικαιολογήσει περισσότερα standups.

Με τα χρόνια, έχω παρατηρήσει κάτι: ο καλύτερος κώδικας που έχω γράψει ποτέ προήλθε από έργα όπου ένιωσα *άδεια για να παίξετε*Ο χειρότερος κώδικας προήλθε από έργα όπου κάθε πληκτρολόγηση ένιωθε σαν να ήταν υπό έλεγχο, όπου προσπαθούσα να γράψω "έτοιμος παραγωγής" κώδικα από το πρώτο λεπτό.

Δεν είναι τυχαίο. **Η δημιουργικότητα και η πίεση δεν αναμειγνύονται καλά.**

Έτσι ανέπτυξα μια προσέγγιση που χωρίζει την ακατάστατη, δημιουργική εξερεύνηση από την στιλβωμένη, έτοιμη για παραγωγή παράδοση.[Κάνε το να δουλέψει, κάνε το όμορφο, κλείδωσέ το.](/blog/makeitworkthenmakeitpretty)" - αλλά πραγματικά, είναι για να δώσεις στον εαυτό σου την άδεια να πειραματιστεί χωρίς το βάρος της τελειότητας να κρέμεται πάνω σου.

[TOC]

## Οι Τρεις Φάσεις

Επιτρέψτε μου να ξεκαθαρίσω εκ των προτέρων: αυτό δεν έχει να κάνει με το να είσαι τσαπατσούλης ή να αποφεύγεις την πειθαρχία. *εφαρμογή της σωστής πειθαρχίας την κατάλληλη στιγμή*.

```mermaid
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 με τον τρόπο που ισχυρίζονται οι γιατροί; (Ποιητής: σπάνια συμπεριφέρεται)
- Μπορώ να λύσω αυτό το πρόβλημα με τα εργαλεία που έχω;
- Ποιες είναι οι πραγματικές απαιτήσεις, όχι αυτές που γράφτηκαν στο εισιτήριο;
- Αυτό είναι ένα πρόβλημα δύο ωρών ή ένα πρόβλημα δύο εβδομάδων;

**Η κρίσιμη διορατικότητα:** Μέχρι να ανεβάσεις δημόσιες σχέσεις, το υποκατάστημα σου είναι *Το δικό σου.*. Είναι πειραματικό εργαστήριο σας. Κανείς δεν χρειάζεται να δείτε ψευδείς εκκινήσεις σας, σχολίασε τον κώδικα αποσφαλμάτωσης σας, σας "TODO: αυτό είναι τρομερό, διορθώστε αργότερα" σχόλια. Αλλά ελέγξτε σε OFTEN[lt] κάθε φορά που έχετε κάτι που λειτουργεί, να δεσμευτεί και να ωθήσει. Συνδέστε στον αγωγό CI σας? ένας καλός αγωγός θα σας δώσει περισσότερες πληροφορίες (εξετάσεις ενσωμάτωσης, σπάζοντας αλλαγές αλλού, κλπ.). Είναι ΜΠΟΡΕΙ να ελέγξετε σε άσχημο, μόλις ράβονται μαζί Franken-code. Αυτό είναι το ΣΗΜΕΙΟ. Ακόμα και ως ένας βετεράνος τριών decade, Εξακολουθώ να το κάνω αυτό.

**Πλευρικό όφελος:** Οι συχνές δεσμεύσεις είναι ένας καρδιακός παλμός. Σε απομακρυσμένες/ασύγχρονες ομάδες, ένα σταθερό μονοπάτι από "άσχημη αλλά προοδευτική" δεσμεύεται καθησυχάζει τους πάντες ότι η εργασία κινείται _BAR_ χωρίς να χρειάζεται να εκτελέσει την παραγωγικότητα στο Slack.

Αυτή η ψυχολογική ελευθερία είναι απαραίτητη. Όταν ξέρετε ότι μπορείτε να πετάξετε τα πάντα μακριά και να ξεκινήσετε εκ νέου, παίρνετε πιο τολμηρές αποφάσεις. Προσπαθείτε αυτή την περίεργη προσέγγιση που πιθανώς δεν θα λειτουργήσει. Μερικές φορές λειτουργεί λαμπρά. Μερικές φορές πρέπει να διαβάσετε ένα άρθρο, να παρακολουθήσετε ένα βίντεο στο YouTube, να σκεφτείτε για λίγο. Θυμηθείτε: η δουλειά σας είναι να παραδώσετε καλό λογισμικό και καλές αποφάσεις, να μην πληκτρολογήσετε γρήγορα. **ΔΕΝ ΕΙΝΑΙ ΤΥΠΙΣΤΟΣ**.

```csharp
// 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 };
}
```

Είναι αυτός ο κώδικας παραγωγής; Θεός όχι. Είναι χρήσιμο; Απολύτως. Σας λέει αν η προσέγγισή σας είναι ακόμη βιώσιμη πριν επενδύσετε ώρες γυαλίζοντας κάτι που δεν θα λειτουργήσει.

**Σημείωση σχετικά με την ανατροφοδότηση:** Μπορείτε να πάρετε σχόλια από *οποιαδήποτε* σημείο της Φάσης 1 *Ποιος είναι αυτός;* Ο στόχος είναι να οικοδομήσουμε το καλύτερο χαρακτηριστικό, όχι να ακολουθήσουμε μια άκαμπτη διαδικασία.

Χτίζοντας ένα API ότι μια άλλη ομάδα θα καταναλώσει; Πάρτε τους εμπλέκονται νωρίς. Ένα γρήγορο "Έχει αυτό το σχήμα έχει νόημα;" συνομιλία μπορεί να σώσει ημέρες της rework. *ματιά* αντί για το αν είναι *έργα*Δεν πειράζει, δίνουν ανατροφοδότηση στη Φάση 2.

Χρησιμοποίησε την κρίση σου. **διευκρίνηση**"Το spec λέει "χειριστείτε τα λάθη χαριτωμένα" does that mean retrike three times, or fail fast and notification?" Αυτό είναι μια συνομιλία Φάσης 1.

Αν η είσοδος των ενδιαφερομένων είναι κρίσιμη, αλλά θα παλέψουν με τον σκληρό κώδικα, θα φτιάξουν ένα ασήμαντο demo. Ένα σκληρό κωδικοποιημένο happy-path που δείχνει ότι η έννοια μπορεί να ξεκλειδώσει πολύτιμες ανατροφοδότηση χωρίς τον περισπασμό των περιπτώσεων που λείπουν.

Ο στόχος είναι το καλύτερο χαρακτηριστικό, όχι η καθαρότητα της διαδικασίας.

### Φάση 2: Κάντε το όμορφο

**Το μάντρα:** Τώρα που ξέρεις τι φτιάχνεις, κάντο. *Ωραία.*.

Επιβεβαίωσες ότι η προσέγγιση λειτουργεί, καταλαβαίνεις το σχήμα του προβλήματος, και πιθανότατα ανακάλυψες μια ντουζίνα υποθέσεις που δεν ανέφερε το αρχικό εισιτήριο.

Τώρα καθαρίστε το.

```csharp
// 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

**Κρίσιμα, αυτή είναι η καλύτερη στιγμή για μη τεχνικές ανατροφοδότηση των ενδιαφερομένων.** Το χαρακτηριστικό είναι πλέον ορατό και χρησιμοποιήσιμο, αλλά δεν έχετε επενδύσει ακόμα ώρες γράφοντας δοκιμές γι 'αυτό. Αν λένε "πραγματικά, θέλαμε να κάνει Χ αντ 'αυτού," μπορείτε να περιστρέψετε χωρίς την καρδιά της διαγραφής μια ολοκληρωμένη δοκιμαστική σουίτα.

### Φάση 3: Κλειδώστε το κάτω

**Το μάντρα:** Σκληρώστε το, δοκιμάστε το, κάντε το ανθεκτικό.

Προσθέστε ελέγχους ασφαλείας. Εφαρμόστε τον κατάλληλο χειρισμό λάθους για τις περιπτώσεις άκρη παραγωγής. Προσθέστε την παρακολούθηση και guardrails.

Γιατί να περιμένουμε μέχρι τώρα; **Επιτέλους ξέρεις τι δοκιμάζεις.**.

Οι δοκιμές που γράφτηκαν στη Φάση 1 θα ήταν λάθος. Δεν καταλάβατε ακόμα το πρόβλημα. Οι δοκιμές που γράφτηκαν στη Φάση 2 θα είχαν ανατραπεί. Εξακολουθούσατε να εξευγενίζετε το API. Οι δοκιμές που γράφτηκαν στη Φάση 3 είναι *σωστό*, διότι η εφαρμογή είναι σταθερή.

```csharp
[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 φορές έξω;)
- Παρακολούθηση (πώς θα γνωρίζουμε αν αυτό σπάει στην παραγωγή;)

## Χορογραφία ανατροφοδότησης

Διαφορετικοί άνθρωποι πρέπει να εμπλέκονται σε διαφορετικά στάδια, να κάνουν λάθος και θα πνιγείς στην τριβή.

```mermaid
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:** Τεχνικές φωνές μόνο devs, αρχιτέκτονες. Μπορούν να δουν πέρα από το χάος στο σχήμα της λύσης. Μη-τεχνικοί ενδιαφερόμενοι θα πανικοβληθούν σε πρόχειρο κώδικα και θα ρωτήσουν για χρώματα κουμπί όταν είστε ακόμα υπολογίζει το μοντέλο δεδομένων.

**Φάση 2:** Μη τεχνικές φωνές ευπρόσδεκτες. Το χαρακτηριστικό είναι χρησιμοποιήσιμο, κουμπιά κάνουν πράγματα. Πάρτε ιδιοκτήτες προϊόντων και σχεδιαστές που εμπλέκονται τώρα οι αλλαγές είναι ακόμα φθηνές, δεν έχετε γράψει δοκιμές ακόμα.

**Φάση 3:** "Κι αν αυτό έχει 10x κίνηση;" "Κι αν κάποιος περάσει κακόβουλη είσοδο;" Αυτές οι ανησυχίες έχουν σημασία μόνο όταν το χαρακτηριστικό λειτουργεί πραγματικά.

## Καταστήματα ως Sandboxes

Εδώ είναι το πράγμα που άλλαξε την αναπτυξιακή μου ζωή: **Μέχρι να ανεβάσεις δημόσιες σχέσεις, το υποκατάστημα σου είναι εντελώς ιδιωτικό.**.

Αυτό μπορεί να ακούγεται προφανές, αλλά βλέπω τους προγραμματιστές να συμπεριφέρονται σε κάθε δέσμευση σαν να πηγαίνει στο μόνιμο φάκελό τους, φοβούνται να πειραματιστούν, φοβούνται να γράψουν άσχημο κώδικα, φοβούνται να σπάσουν τα πράγματα.

Θέλω να σκεφτείς το κλαδί σου σαν μια παιδική χαρά, ένα εργαστήριο όπου αναμένονται εκρήξεις και κανείς δεν πληγώνεται.

```mermaid
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` σχόλια παντού
- Αφήστε τον κωδικό αποσφαλμάτωσης στη θέση του ενώ εξερευνάτε
- Έχετε πολλαπλές "δοκιμάστε αυτή την προσέγγιση" δεσμεύεται
- Μην ανησυχείτε για τη δέσμευση μηνυμάτων (θα λιώσετε αργότερα)

Τότε, πριν ανεβάσεις δημόσιες σχέσεις

1. Καθαρίστε τον κωδικό (Φάση 2)
2. Προσθήκη δοκιμών (σχάση 3)
3. Interactive rebased to squash all that backsy history
4. Γράψτε ένα σωστό μήνυμα δέσμευσης

Οι κριτικοί των δημοσίων σχέσεων βλέπουν καθαρό, επαγγελματικό κώδικα με μια σαφή αφήγηση. Δεν βλέπουν τα έξι ψευδή ξεκινά, το 3π.μ. "γιατί αυτό το αιματηρό έργο" διαπράττει, ή η "μη αναιρέσει" ιστορία.

> **Το γυαλιστερό κουτί παραμένει ιδιωτικό.**

## Γιατί Αυτό Μειώνει το Άγχος

Κάθε γραμμή αισθάνεται μόνιμη. Κάθε απόφαση αισθάνεται ως συνέπεια. Αυτή η προσέγγιση αφαιρεί την πίεση που περιέχει το χάος: **Το παιχνίδι είναι νόμιμη δουλειά.** στη Φάση 1, η ανάδραση έρχεται όταν η περιστροφή είναι φθηνή στη Φάση 2, και οι δοκιμές γράφονται μια φορά σωστά στη Φάση 3 επειδή τελικά ξέρετε τι δοκιμάζετε.

> **"Το παιχνίδι είναι νόμιμη δουλειά."**

Το TDD λειτουργεί έξοχα όταν ο τομέας είναι καλά κατανοητός ή είστε καρφώνοντας κάτω από ένα γνωστό σφάλμα. Αλλά όταν το πρόβλημα εξακολουθεί να είναι θολή, γράφοντας δοκιμές για πρώτη φορά σημαίνει δοκιμή το λάθος πράγμα τρεις φορές στη σειρά. Αυτή η προσέγγιση παρακάμπτει ότι: εξερευνήστε πρώτα, δοκιμή όταν είναι σταθερή.

## Pragmatic SOLID (και το βρώμικο μυστικό του DRY)

Τι κάνει αυτή η τρισδιάστατη νοοτροπία στις αρχές του σχεδιασμού σας; Με λίγα λόγια: σκοτώνει το δόγμα. Εδώ είναι πώς αλλάζει τη σκέψη μου για SOLID, DRY, και διεπαφές σε C#.

Ανέφερα την εφαρμογή των αρχών SOLID "πραγματικά" στη Φάση 2. Ας είμαι συγκεκριμένος σχετικά με το τι εννοώ.

Το SOLID είναι ένα καλό σύνολο αρχών. Τους διδάσκω, τις χρησιμοποιώ, αλλά έχω δει ομάδες να εξαφανίζονται αφαιρώντας τρύπες κουνελιού στο όνομα της Ενιαίας Ευθύνης ή Open/Closed, δημιουργώντας συστήματα τόσο ευέλικτα που είναι ακατανόητα.

Εδώ είναι η ουριστική μου:

**Εφαρμόστε το SOLID όταν:**

- Έχεις αποδείξεις ότι θα χρειαστείς την ευελιξία.
- Η αφαίρεση κάνει τον κώδικα πιο ξεκάθαρο
- Φτιάχνεις κάτι που οι άλλοι θα χρησιμοποιήσουν.

**Μην εφαρμόσετε το SOLID όταν:**

- Εικάζεις για μελλοντικές απαιτήσεις.
- Η αφαίρεση προσθέτει πολυπλοκότητα χωρίς σαφήνεια
- Φτιάχνεις εσωτερικό κώδικα που μόνο εσύ διατηρείς.

Τρεις παρόμοιες γραμμές κώδικα είναι καλύτερες από μια πρόωρη αφαίρεση. Μπορείτε πάντα να εξάγετε αργότερα όταν βλέπετε το μοτίβο καθαρά. Δεν μπορείτε εύκολα να αναιρέσετε μια αφαίρεση που είναι ψημένη στην αρχιτεκτονική σας.

```csharp
// 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 ήταν λάθος εδώ)

```csharp
// "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 είναι ένας εφιάλτης για να διατηρηθεί επειδή προσπαθεί να είναι όλα τα πράγματα για όλους τους καλούντες. Κάθε αλλαγή απαιτεί την κατανόηση όλων των περιπτώσεων χρήσης.

**Ο κανόνας μου:** Ανέχεστε την επικάλυψη μέχρι να δείτε το *ίδια* πράγμα τρεις φορές και είστε σίγουροι ότι αντιπροσωπεύει το *ίδια* Μια ιδέα που θα εξελιχθεί μαζί. Μέχρι τότε, ένα κομμάτι της αντιγραφής-επικόλλησης είναι μια χαρά. Δεν είναι μια ηθική αποτυχία, είναι η κατάλληλη προσοχή.

Μπορείτε πάντα να αποτυπώσετε αργότερα όταν το μοτίβο είναι σαφές. Δεν μπορείτε εύκολα να αναιρέσετε μια κακή αφαίρεση που είναι υφασμένο μέσω της βάσης κώδικα σας.

### Το πρόβλημα διεπαφής στο C#

Εδώ είναι μια άλλη ιερή αγελάδα που χρειάζεται σφαγή: **το μοτίβο "κάθε τάξη χρειάζεται μια διεπαφή"**.

Πίσω στις πρώτες ημέρες της ένεσης εξάρτησης .NET, κάποιος αποφάσισε ότι για να κάνει τον κώδικα δοκιμάζεται, θα έπρεπε να εγχύσει διεπαφές παντού. Η λογική ήταν: δεν μπορείτε να κοροϊδέψετε μια τάξη σκυροδέματος, έτσι ώστε κάθε υπηρεσία χρειάζεται `IService` και `ServiceImpl`Ξαφνικά κάθε βάση κώδικα C# ήταν γεμάτη με διασυνδέσεις μιας εφαρμογής που υπήρχαν αποκλειστικά για δοκιμές.

```csharp
// 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
```

Αυτό ήταν ο προγραμματισμός της λατρείας φορτίων. *θεωρητική* Δοκιμασία και *θεωρητική* μελλοντική ευελιξία που σπάνια υλοποιήθηκε.

**Αυτό είναι το θέμα: δεν το χρειάζεσαι πια αυτό.**

Σύγχρονα πλαίσια κοροϊδίας όπως [N Substituto](https://nsubstitute.github.io/) και [ΜοκCity name (optional, probably does not need a translation)](https://github.com/moq/moq4) Το πιο σημαντικό είναι ότι το δοχείο DI του ASP.NET Core λειτουργεί απόλυτα καλά με τύπους σκυροδέματος:

```csharp
// 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. Προσθήκη διεπαφής *όταν το χρειάζεσαι για να το δοκιμάσεις.*

Το τρίτο σημείο είναι κρίσιμο: **τα εργαλεία αναπαραγωγιμότητας είναι εξαιρετικά καλά στην εξαγωγή διεπαφών**.

Στο Rider ή στο Visual Studio, είναι κυριολεκτικά `Ctrl+.` → "Extract Interface" και είστε έτοιμοι. Κάθε μέθοδος εξάγεται, η τάξη ενημερώνεται για την εφαρμογή του, και μπορείτε να βρείτε/αντικαταστήσετε όλες τις χρήσεις αν χρειαστεί.

```csharp
// 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 { ... }
```

Η δουλειά μου ρέει τώρα: **Διεπαφές έρχονται στη Φάση 3, όχι στη Φάση 1**.

Κατά τη διάρκεια του "να το κάνετε να λειτουργήσει" και "να το κάνει όμορφο," απλά γράφω τάξεις. Δεν πρόωρη αφαίρεση. Δεν υπάρχει φόρος διεπαφής σε κάθε τύπο. Ο κώδικας είναι απλούστερος, ευκολότερος να πλοηγηθεί (δεν άλμα μεταξύ `IFoo` και `Foo`), και γρηγορότερα για να γράψετε.

Όταν χτυπήσω το "κλείδωσέ το" και πρέπει να γράψω εξετάσεις, *τότε* Συνήθως είναι λιγότερο από ό, τι θα σκεφτόσουν συχνά μόνο εξωτερικές ενότητες όπως πελάτες HTTP, βάσεις δεδομένων, και ουρές μηνυμάτων.

Οι εξετάσεις είναι πιο πολύτιμες ούτως ή άλλως επειδή δοκιμάζουν την πραγματική συμπεριφορά, όχι ένα χλευασμό του τι εγώ *Σκέψου.* η συμπεριφορά θα πρέπει να είναι.

```csharp
// 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" που έχω συναντήσει. Εργαζόμενο λογισμικό γρήγορα (Φάση 1).Απαντώντας στην αλλαγή (Φάσεις 1-2). Η συνεργασία των πελατών όταν το χαρακτηριστικό είναι αποδεδειγμένο (Φάση 2).

Το σύγχρονο "Agile" έχει συλληφθεί από τους λάτρεις της διαδικασίας που έχουν προσθέσει τόσες πολλές τελετές δεν υπάρχει χρόνος για εξερεύνηση. Αυτή η προσέγγιση φέρνει πίσω παιχνίδι όχι ως επιείκεια, αλλά ως μια νόμιμη φάση δημιουργικής εργασίας.

## Όταν ΔΕΝ χρησιμοποιείται αυτή η προσέγγιση

Πρέπει να είμαι ειλικρινής: αυτή δεν είναι πάντα η σωστή προσέγγιση.

**Επιδιορθώσεις σφαλμάτων:** Όταν φτιάχνεις ένα γνωστό έντομο, το στυλ TDD βγάζει νόημα. Γράψε ένα τεστ που αναπαράγει το έντομο, το φτιάχνεις, το τελειώνεις.

**Κατανοητό πρόβλημα:** Αν εφαρμόζετε έναν τυποποιημένο αλγόριθμο ή ένα μοτίβο που έχετε χρησιμοποιήσει εκατό φορές, δεν χρειάζεστε μια φάση εξερεύνησης.

**Σφιχτές προθεσμίες με γνωστές απαιτήσεις:** Αν πραγματικά ξέρεις ακριβώς τι φτιάχνεις και όταν χρειάζεται να στείλεις, μπορεί να αποφύγεις την εξερεύνηση της Φάσης 1.

**Υψηλά ρυθμιζόμενα περιβάλλοντα:** Αν κάθε δέσμευση χρειάζεται να υπογράψει, sandboxing είναι πιο δύσκολο. Αν και μπορείτε ακόμα να κάνετε την εξερεύνηση τοπικά πριν σπρώξετε.

Αλλά για *περισσότερο* feature development όπου οι απαιτήσεις είναι λίγο ασαφείς, η τεχνική προσέγγιση δεν είναι προφανής, και χρειάζεστε χώρο για να σκεφτείτε αυτή η προσέγγιση λειτουργεί έξοχα.

## Σημείωση για τους νέους προγραμματιστές

Αν είστε νωρίς στην καριέρα σας, μπορεί να αισθάνεστε πίεση για να δείξετε "τέλειο" κώδικα όλη την ώρα. Δεν χρειάζεται να. Χρησιμοποιήστε αυτή την τριφασική προσέγγιση απλά να είναι σαφής σχετικά με το ποια φάση είστε σε, και πάντα να τελειώσει τη δουλειά σε όλη τη φάση 3. Κανείς δεν θα σας κατηγορήσει για βρώμικο κώδικα εξερεύνησης αν οδηγεί σε γυαλισμένο, δοκιμασμένο, έτοιμο κώδικα παραγωγής στο τέλος.

## Κάτω γραμμή

**Κάντε το να δουλέψει, κάντε το όμορφο, κλειδώστε το.**

Αρχίστε απλά. Σχεδιάστε με προνοητικότητα αλλά όχι κερδοσκοπία. Εξετάστε γρήγορα. Προσθέστε την πολυπλοκότητα μόνο όταν αποπληρώνεται.

> **"Η σύλληψη είναι πολύ φθηνότερη από τη λάθος αφαίρεση."**

Η φάση εξερεύνησης δεν είναι ένα ένοχο μυστικό είναι ένα καθορισμένο μέρος της διαδικασίας. Παρακλάδι σας είναι σας sandbox μέχρι να αυξήσετε μια PR. Τεχνική ανατροφοδότηση έρχεται νωρίς, μη τεχνική ανατροφοδότηση έρχεται μέσα, ops και ανατροφοδότηση ασφάλειας έρχεται αργά.

Αυτό κρατάει ζωντανή τη δημιουργικότητα, μειώνει το άγχος και παράγει συστήματα που κλιμακώνονται κομψά γιατί καταλάβατε το πρόβλημα πριν δεσμευτείτε για τη λύση.

> **Σταμάτα να προσπαθείς να είσαι τέλειος από την πρώτη στιγμή.**

---


## Σχετική Ανάγνωση

Αν αυτό αντηχεί, θα μπορούσατε επίσης να απολαύσετε αυτές τις σχετικές θέσεις:

- **[Πώς φτιάχνω το λογισμικό](/blog/makeitworkthenmakeitpretty)** - Πηγαίνει βαθύτερα στη δοκιμαστική φιλοσοφία πίσω από αυτή την προσέγγιση, με λεπτομερή παραδείγματα κώδικα των τριών φάσεων.
- **[Η καθημερινή στάση είναι μαλακίες (εκτός αν κερδίζουν το μισθό τους)](/blog/agile-standups-ceremony-tax)** - Η γνώμη μου για το γιατί η τελετή είναι φόρος, και πώς να αξιολογήσει αν οι ευκίνητες τελετουργίες σας κερδίζουν την κράτησή τους.
- **[Ευγηρία Speccing: Γράφοντας Spec χαρακτηριστικά που λειτουργούν στην πραγματικότητα](/blog/writingfeaturespecs)** - Επειδή οι προδιαγραφές είναι εργαλεία, όχι βιβλιάρια και να τους μεταχειρίζεσαι ως ζωντανά έγγραφα είναι ο μόνος τρόπος για να φτιάξεις το σωστό.
- **[Εργασία σε Κληρονομικές Βάσεις](/blog/workingonlegacysystems)** - Πρακτικά μαθήματα από δεκαετίες ζωής ανάδοχων, συμπεριλαμβανομένου του πώς να εφαρμόσεις αυτές τις αρχές όταν μαθαίνεις ένα σύστημα που δεν έχτισες.