Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Muitas vezes, vários algoritmos ou implementações podem resolver a mesma tarefa corretamente. O código que você escolhe comunica um design. Desenvolvedores e agentes de codificação que leem seu código usam sua estrutura para inferir sua intenção: o que cada operação precisa, onde ela pertence e quando é executada.
Dica
Este artigo faz parte da seção Conceitos Básicos para desenvolvedores que conhecem expressões, instruções e métodos básicos em C#. Se você é novo em programação, comece com os tutoriais Comece agora.
Esta seção compara abordagens válidas para algoritmos e explica diferenças sutis em seu comportamento e como os leitores as interpretam. Você combinará elementos de linguagem familiares, como tipos, expressões, instruções e métodos, e escolherá formas que comuniquem o design que você pretende.
Comparar o comportamento e a intenção de design
Antes de escolher uma implementação, considere como outro código a usará. Esse código é o chamador: ele fornece entradas, invoca sua operação e usa os resultados. Isso precisa de um resultado concluído imediatamente ou pode processar valores um de cada vez? Um auxiliar faz parte apenas dessa operação ou outras operações também a usarão? Essas perguntas ajudam você a comparar tanto o comportamento necessário quanto o design que seu código comunica.
Uma operação inline curta mostra aos leitores as etapas no ponto de uso. Por exemplo, Where(task => task.Priority > 3) torna a regra de filtragem imediatamente visível. Um auxiliar nomeado, como em Where(IsUrgent), enfatiza a finalidade da regra e permite que os leitores inspecionem seus detalhes separadamente. Escolha com base em se as etapas ou sua finalidade melhor explicam o código ao redor.
O local em que você declara um helper comunica seu escopo pretendido. Uma função local dentro de ShowDashboard indica que o auxiliar pertence a essa operação e impede que outros membros a chamem. Um método privado no tipo que contém disponibiliza o auxiliar para outros membros, sugerindo que você espera reutilizar em outro lugar do tipo. Ambos podem organizar o mesmo cálculo, mas levam os leitores a expectativas diferentes sobre sua função.
Como você fornece as entradas de um auxiliar comunica suas dependências. Passar um limite como um parâmetro, como IsUrgent(task, minimumPriority), torna essa entrada visível em cada chamada. Uma função local ou lambda pode, em vez disso, capturarminimumPriority, usando a variável de seu escopo ao redor sem recebê-la como um argumento. A captura conecta o auxiliar ao contexto da operação que contém; parâmetros explícitos facilitam a identificação dos dados fornecidos. Use uma static função local ou lambda quando quiser garantir que ela não capture o estado ao redor.
A forma como você retorna os resultados comunica quando o trabalho acontece. Um método que constrói e retorna uma coleção conclui esse trabalho antes que o chamador receba a coleção. Um iterador produz elementos conforme o chamador os solicita, para que o chamador possa parar antes que todos os elementos sejam produzidos. Ambos podem produzir os mesmos valores, mas os chamadores precisam entender se estão recebendo resultados concluídos ou uma sequência cujo processamento ainda ocorrerá.
Para cada opção, primeiro identifique o comportamento de que o código de chamada precisa. Em seguida, considere o que outro desenvolvedor ou agente de codificação inferirá do nome, escopo, entradas e resultado. Escolha uma forma que faça com que essas expectativas correspondam ao seu design e explique um comportamento importante 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 forma em todos os lugares.
Conheça opções de design em um exemplo
O exemplo a seguir ilustra como várias opções comunicam diferentes partes de um design. Ele exibe tarefas urgentes e prontas para serem iniciadas. Um ProjectTask contém o título, a prioridade e o status de bloqueio de uma tarefa.
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 opção mantém uma parte diferente do trabalho clara:
- O lambda passado para
Wherecoloca uma regra de prioridade curta ao lado da operação que a usa. - A função
FormatTasklocal nomeia uma etapa de formatação que pertence apenas aShowDashboard. - O iterador
ReadyTaskspermite que os chamadores consumam tarefas prontas sem criar uma coleção intermediária. - O
foreachloop torna a ação final, escrevendo cada resultado, explícita.
Para comparação, Where(static task => !task.IsBlocked) pode substituir ReadyTasks porque esse teste de preparação é um filtro simples. Ambos os formulários postergam a aplicação do filtro até a enumeração. O lambda coloca a regra no local da chamada; ReadyTasks dá um nome à regra. Escolha entre eles com base em quais informações ajudam os leitores a entender a pessoa que ligou.
Os artigos a seguir usam comparações como essas para explicar quando cada abordagem se encaixa e o que os leitores podem inferir de sua escolha.