Eu estava lendo nesta postagem do blog sobre o anti-padrão for-if, e não tenho muita certeza de entender por que é um anti-padrão.
foreach (string filename in Directory.GetFiles("."))
{
if (filename.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))
{
return new StreamReader(filename);
}
}
Questão 1:
É por return new StreamReader(filename);
dentro for loop
? ou o fato de você não precisar de um for
loop neste caso?
Como o autor do blog apontou, a versão menos louca disso é:
if (File.Exists("desktop.ini"))
{
return new StreamReader("desktop.ini");
}
ambos sofrem de uma condição de corrida porque, se o arquivo for excluído antes da criação do StreamReader
, você receberá um FileNotFoundException
.
Questão 2:
Para corrigir o segundo exemplo, você o reescreveria sem a instrução if e, em vez disso, o cercaria StreamReader
com um bloco try-catch, e se ele der um, FileNotFoundException
você o manipula no catch
bloco de acordo.
To fix the second example, would you re-write it without the if statement, and instead surround the StreamReader with a try-catch block, and if it throws a FileNotFoundException you handle it in the catch block accordingly?
- Sim, é exatamente o que eu faria. Resolver a condição de corrida é mais importante do que alguma noção de "exceções como fluxo de controle" e a resolve de maneira elegante e limpa.null
? Geralmente, você pode usar o LINQ para limpar um código parecido com o seu exemplo:return Directory.GetFiles(".").FirstOrDefault(fileName => fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))?.Select(fileName => new StreamReader(filename));
Observe o?.
operador entre as duas chamadas do LINQ. Além disso, as pessoas podem argumentar que a criação de objetos como esse não é o uso mais apropriado do LINQ, mas acho que está bem aqui. Esta não é uma resposta para sua pergunta, mas discorda em uma parte dela.Respostas:
Este é um antipadrão, pois assume a forma de:
e pode ser substituído por
Um exemplo clássico disso é um código como:
Quando o seguinte funciona bem:
Se você estiver executando um loop e um
if
ouswitch
, pare e pense no que está fazendo. Você está complicando demais as coisas e o loop e o teste completos podem ser substituídos por uma simples linha "just do it". Às vezes, porém, você encontrará que precisa fazer esse loop (mais de um item pode corresponder à única condição, por exemplo); nesse caso, o padrão está correto.É por isso que é um antipadrão: ele pega o padrão "loop and test" e o abusa.
Em relação à sua segunda pergunta: sim. Um padrão "try do" é mais robusto que um padrão "test then do" em qualquer situação em que seu código não seja o único thread em todo o dispositivo que pode alterar o estado do item em teste.
O problema com este código:
é que, no período entre
File.Exists
eStreamReader
tentando abrir esse arquivo, outro encadeamento ou processo pode excluir o arquivo. Então você terá uma exceção. Portanto, essa exceção precisa ser protegida contra algo como:@Flater levanta um bom ponto? Isso em si é um antipadrão? Estamos usando exceções como fluxo de controle?
Se o código ler algo como:
então eu de fato usaria exceções como um goto glorificado e seria de fato um antipadrão. Mas, neste caso, estamos apenas contornando o comportamento indesejável de criar um novo fluxo, portanto, este não é um exemplo desse antipadrão. Mas é um exemplo de como contornar esse antipadrão.
Obviamente, o que realmente queremos é uma maneira mais elegante de criar o fluxo. Algo como:
E eu recomendo agrupar esse
try catch
código em um método utilitário como este, se você estiver usando muito esse código.fonte
Try
, por exemploreturn Try(() => new StreamReader("desktop.ini")).OrElse(null);
, mas o C # não suporta essa construção (sem usar uma biblioteca de terceiros), por isso temos que trabalhar com a versão desajeitada.File.Open
lançará um se não conseguir encontrar o arquivo. Como já temos uma exceção excepcional sendo lançada, prendê-la como parte do fluxo de controle é uma necessidade (a menos que alguém queira simplesmente deixar o aplicativo travar).Maybe<Stream>
tipo que retornanothing
se o arquivo não existir e um fluxo, se existir.Sim, porque você não precisa fazer sua própria pesquisa por itens armazenados em um subsistema consultável.
Em resumo, o sistema de arquivos, como um banco de dados, é capaz de responder a consultas. Ao interagir com um subsistema passível de consulta, temos uma escolha fundamental sobre se queremos que esse subsistema enumere seu conteúdo para nós, a fim de executar a correspondência fora do subsistema ou usar os recursos de consulta nativos do subsistema.
Vamos fingir por um momento que você está procurando um registro em um banco de dados em vez de um arquivo em um diretório em um sistema de arquivos. Você prefere ver
e depois no loop (por exemplo, em C #) sobre o cursor retornado procurando ID = 100 ou deixando o subsistema consultável fazer o possível para encontrar exatamente o que você está procurando?
Eu acho que a maioria de nós escolheria corretamente deixar o subsistema executar a consulta exata de interesse. A alternativa envolve potencialmente numerosas viagens de ida e volta com o subsistema, teste de igualdade ineficiente e renúncia ao uso de quaisquer índices ou outros aceleradores de pesquisa, fornecidos por bancos de dados e sistemas de arquivos.
Sim, porque é assim que essa API específica funciona - não é realmente a nossa escolha, pois essa é uma função da biblioteca. A verificação if, antes da chamada, não oferece valor adicional: precisamos usar o try / catch de qualquer maneira, pois (1) outros erros além do FileNotFound podem ocorrer e (2) a condição de corrida.
fonte
O leitor de stream não tem nada a ver com isso. O anti-padrão surge devido a um claro conflito de intenção entre o
foreach
e oif
:Qual é o propósito do
foreach
?Suponho que sua resposta será algo como: "Quero executar repetidamente um determinado pedaço de código"
Quantos arquivos você espera processar?
Como você pode ter apenas um nome de arquivo específico (incluindo extensão) em uma pasta específica, isso prova que seu código pretende encontrar um arquivo aplicável.
Isso também é confirmado pelo fato de você estar retornando um valor imediatamente. Na verdade, você não se importa com uma segunda partida, mesmo que ela exista.
Há situações em que isso não é um anti-padrão.
Directory.GetFiles(".", SearchOption.AllDirectories)
), é possível encontrar mais de um arquivo com o mesmo nome de arquivo (incluindo extensão)"Test_"
, ou todo"*.zip"
arquivo.Observe que esses dois casos exigiriam que você processasse várias correspondências e, portanto, não retornasse um valor imediatamente.
Exceções são caras. Eles nunca devem ser usados no lugar da lógica de fluxo adequada. Exceções são, como o nome sugere circunstâncias excepcionais .
Por esse motivo, você não deve remover o
if
.Conforme esta resposta no SoftwareEngineering.SE :
Como um resumo rápido do motivo, geralmente, é um antipadrão:
Se você precisa agrupar isso em uma tentativa / captura, depende muito da sua situação:
Nada é sempre uma questão de "sempre usá-lo". Para provar meu argumento:
Então, por que não estamos todos usando esse equipamento de segurança o tempo todo?
A resposta simples é que existem desvantagens em usá-las:
Agora estamos chegando a algum lugar: existem prós e contras . Em outras palavras, só faz sentido usar este equipamento nos casos em que os profissionais superam os contras.
Você deve encerrar a chamada em uma tentativa / captura? Isso depende muito se os benefícios de fazê-lo superam o custo de sua implementação.
Observe que outros podem argumentar que são necessárias apenas algumas teclas para envolvê-lo, portanto, obviamente, isso deve ser feito. Mas esse não é o argumento inteiro:
Então a escolha é sua. Existe algum benefício em fazer isso? Você acha que isso melhora o aplicativo, mais do que o esforço para custear sua implementação?
fonte