Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Muitas vezes, vários algoritmos ou implementações conseguem resolver corretamente a mesma tarefa. O código que escolhes transmite uma conceção. Os programadores e agentes de programação que leem o seu código usam a sua estrutura para inferir a sua intenção: do que cada operação precisa, onde ela pertence e quando é executada.
Tip
Este artigo faz parte da secção Fundamentos para programadores que conhecem expressões, instruções e métodos básicos em C#. Se és novo na programação, começa primeiro pelos tutoriais para começar .
Esta secção compara abordagens válidas aos algoritmos e explica diferenças subtis no seu comportamento e como os leitores as interpretam. Vai combinar elementos linguísticos familiares, como tipos, expressões, instruções e métodos, e escolher formas que comuniquem o design que pretende.
Comparar comportamento e intenção de design
Antes de escolher uma implementação, considere como outros códigos a vão usar. Esse código é o que chama: fornece entradas, invoca a sua operação e utiliza os resultados. Precisa de um resultado concluído imediatamente, ou consegue processar os valores um de cada vez? Um ajudante faz parte apenas desta operação, ou outras operações também o utilizam? Estas perguntas ajudam-no a comparar tanto o comportamento de que precisa como o design que o seu código comunica.
Uma curta operação inline mostra aos leitores os passos no ponto de utilização. Por exemplo, Where(task => task.Priority > 3) torna a regra de filtragem imediatamente visível. Um ajudante nomeado, como em Where(IsUrgent), enfatiza o propósito da regra e permite aos leitores inspecionar os seus detalhes separadamente. Escolha com base em se os passos ou o seu propósito explicam melhor o código envolvente.
Onde declaras uma função auxiliar comunica o âmbito pretendido. Uma função local no interior de ShowDashboard indica que a função auxiliar pertence a essa operação e impede que outros membros a chamem. Um método privado no tipo que o contém torna a função auxiliar disponível para outros membros, sugerindo que espera reutilização noutro local do tipo. Ambos podem organizar o mesmo cálculo, mas levam os leitores a expectativas diferentes quanto ao seu papel.
A forma como fornece as entradas de um helper comunica as suas dependências. Passar um limite como parâmetro, como IsUrgent(task, minimumPriority), torna essa entrada visível em cada chamada. Uma função local ou lambda pode, em vez disso, minimumPriority, usando a variável do seu âmbito envolvente sem a receber como argumento. Capture liga o helper ao contexto da operação que contém; parâmetros explícitos tornam os dados fornecidos mais fáceis de identificar. Usa uma static função local ou lambda quando quiseres garantir que não captura o estado circundante.
A forma como devolves os resultados comunica quando o trabalho acontece. Um método que constrói e devolve uma coleção conclui esse trabalho antes de o chamador receber a coleção. Um iterador produz elementos à medida que o chamador os solicita, para que o chamador possa parar antes de todos os elementos serem produzidos. Ambos podem produzir os mesmos valores, mas os chamadores precisam de perceber se estão a receber resultados completos ou uma sequência cujo cálculo ainda está por fazer.
Para cada escolha, identifica primeiro o comportamento de que o código que faz a chamada precisa. Depois, considere o que outro programador ou agente de programação vai inferir do nome, âmbito, entradas e resultado. Escolha uma forma que faça essas expectativas corresponderem ao seu design e explique comportamentos importantes que a forma do código por si só não revela. O objetivo é fazer uma escolha deliberada entre alternativas válidas, não aplicar uma única forma em todo o lado.
Leia as escolhas de design num exemplo
O exemplo seguinte ilustra como várias escolhas comunicam diferentes partes de um design. Mostra tarefas urgentes e prontas a começar. Uma ProjectTask contém o título da tarefa, a prioridade e o estado de bloqueio.
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);
Cada escolha mantém uma parte diferente do trabalho clara:
- A lambda passada a
Wherecoloca uma regra de prioridade curta ao lado da operação que a utiliza. - A função
FormatTasklocal nomeia um passo de formatação que pertence apenas aShowDashboard. - O iterador
ReadyTaskspermite que os chamadores consumam tarefas prontas sem criar uma coleção intermédia. - O
foreachloop torna explícita a ação final, escrevendo cada resultado.
Para comparação, Where(static task => !task.IsBlocked) poderia substituir ReadyTasks porque este teste de prontidão é um filtro simples. Ambas as formas adiam o filtro até à enumeração. O lambda coloca a regra no local da chamada; ReadyTasks dá um nome à regra. Escolha entre eles com base na informação que ajuda os leitores a compreender o chamador.
Os artigos que se seguem usam comparações deste tipo para explicar quando cada abordagem se encaixa e o que os leitores podem inferir da sua escolha.