Creare software

Spesso più algoritmi o implementazioni possono risolvere correttamente la stessa attività. Il codice scelto comunica un design. Gli sviluppatori e gli agenti di coding che leggono il codice usano la struttura per dedurre la finalità: quali sono le esigenze di ogni operazione, dove appartiene e quando viene eseguita.

Tip

Questo articolo fa parte della sezione Nozioni fondamentali per gli sviluppatori che conoscono espressioni, istruzioni e metodi C# di base. Se non si ha familiarità con la programmazione, iniziare prima con le esercitazioni introduttive .

In questa sezione vengono confrontati gli approcci validi per gli algoritmi e vengono illustrate le sottili differenze nel loro comportamento e il modo in cui i lettori li interpretano. Si combinano elementi del linguaggio familiari, ad esempio tipi, espressioni, istruzioni e metodi, e si scelgono forme che comunicano la progettazione desiderata.

Confrontare il comportamento e la finalità di progettazione

Prima di scegliere un'implementazione, valutare il modo in cui altro codice la userà. Tale codice è il codice chiamante: fornisce input, richiama l'operazione e usa i risultati. È necessario un risultato completato immediatamente o può elaborare i valori uno alla volta? Un helper fa parte solo di questa operazione o sarà usato anche da altre operazioni? Queste domande consentono di confrontare sia il comportamento necessario che il design che il codice comunica.

Un'operazione inline breve mostra i passaggi da eseguire al momento dell'utilizzo. Ad esempio, Where(task => task.Priority > 3) rende immediatamente visibile la regola di filtro. Un helper con nome, come in Where(IsUrgent), enfatizza lo scopo della regola e consente ai lettori di esaminarne i dettagli separatamente. Scegliere in base al fatto che i passaggi o il loro scopo spieghino meglio il codice circostante.

Dove si dichiara un helper comunica l'ambito previsto. Una funzione locale all'interno di ShowDashboard indica che l'helper appartiene a tale operazione e impedisce ad altri membri di chiamarlo. Un metodo privato sul tipo contenitore rende disponibile l'helper ad altri membri, suggerendo che ne prevedi il riutilizzo altrove nel tipo. Entrambi possono organizzare lo stesso calcolo, ma portano i lettori a aspettative diverse sul suo ruolo.

La modalità di fornitura degli input di un helper comunica le relative dipendenze. Il passaggio di un vincolo come parametro, ad esempio IsUrgent(task, minimumPriority), rende l'input visibile a ogni chiamata. Una funzione locale o lambda può invece catturareminimumPriority, usando la variabile dall'ambito circostante senza riceverla come argomento. Capture connette l'helper al contesto dell'operazione contenitore; I parametri espliciti semplificano l'identificazione dei dati forniti. Usare una static funzione locale o un'espressione lambda quando si vuole imporre che non acquisisca lo stato circostante.

La modalità di restituzione dei risultati comunica quando avviene l'elaborazione. Un metodo che compila e restituisce una raccolta completa tale lavoro prima che il chiamante riceva la raccolta. Un iteratore produce elementi quando il chiamante li richiede, in modo che il chiamante possa arrestarsi prima che vengano generati tutti gli elementi. Entrambi possono produrre gli stessi valori, ma i chiamanti devono capire se ricevono risultati completati o una sequenza il cui calcolo deve ancora avvenire.

Per ogni scelta, identificare innanzitutto il comportamento necessario al codice chiamante. Si consideri quindi quale altro sviluppatore o agente di codifica dedurrà dal nome, dall'ambito, dagli input e dal risultato. Scegliere un modulo che faccia corrispondere tali aspettative alla progettazione e spiegare un comportamento importante che la forma del codice da sola non rivela. L'obiettivo è fare una scelta deliberata tra alternative valide, non applicare una forma ovunque.

Leggere le scelte di progettazione in un esempio

Nell'esempio seguente viene illustrato il modo in cui diverse scelte comunicano parti diverse di una progettazione. Visualizza le attività urgenti e pronte per l'avvio. Un ProjectTask comprende il titolo, la priorità e lo stato di blocco di un'attività.

var tasks = new[]
{
    new ProjectTask("Confirm venue", Priority: 3, IsBlocked: false),
    new ProjectTask("Print badges", Priority: 2, IsBlocked: true),
    new ProjectTask("Test microphones", Priority: 2, IsBlocked: false),
};

ShowDashboard("Community meetup", tasks, minimumPriority: 2);

static void ShowDashboard(
    string projectName,
    IEnumerable<ProjectTask> tasks,
    int minimumPriority)
{
    var urgentTasks = tasks.Where(
        task => task.Priority >= minimumPriority);

    foreach (ProjectTask task in ReadyTasks(urgentTasks))
    {
        Console.WriteLine(FormatTask(task));
    }

    string FormatTask(ProjectTask task) =>
        $"{projectName}: {task.Title}";
}

static IEnumerable<ProjectTask> ReadyTasks(
    IEnumerable<ProjectTask> tasks)
{
    foreach (ProjectTask task in tasks)
    {
        if (!task.IsBlocked)
        {
            yield return task;
        }
    }
}

record ProjectTask(string Title, int Priority, bool IsBlocked);

Ogni scelta mantiene chiara una parte diversa del lavoro:

  • L'espressione lambda passata a Where inserisce una regola di priorità breve accanto all'operazione che la usa.
  • La funzione locale FormatTask indica un passaggio di formattazione che appartiene solo a ShowDashboard.
  • L'iteratore ReadyTasks consente ai chiamanti di elaborare attività pronte senza creare una raccolta intermedia.
  • Il foreach ciclo rende esplicita l'azione finale, scrivendo ogni risultato.

Per il confronto, Where(static task => !task.IsBlocked) potrebbe sostituire ReadyTasks perché questo test di idoneità è un filtro semplice. Entrambe le forme rinviano il filtro fino all'enumerazione. L'espressione lambda inserisce la regola nel sito di chiamata; ReadyTasks assegna alla regola un nome. Scegliere tra di essi in base alle informazioni che consentono ai lettori di comprendere il chiamante della funzione.

Gli articoli che seguono usano confronti come questi per spiegare quando ciascun approccio è adatto e cosa i lettori possono dedurre dalla tua scelta.

Passaggi successivi