Contexto
No Código Limpo , página 35, diz
Isso implica que os blocos dentro de instruções if, else, while e etc. devem ter uma linha. Provavelmente essa linha deve ser uma chamada de função. Isso não apenas mantém a função anexa pequena, mas também agrega valor documental, porque a função chamada dentro do bloco pode ter um nome bem descritivo.
Concordo completamente, isso faz muito sentido.
Mais tarde, na página 40, ele diz sobre argumentos de função
O número ideal de argumentos para uma função é zero (niládico). A seguir vem um (monádico), seguido de perto por dois (diádico). Três argumentos (triádicos) devem ser evitados sempre que possível. Mais de três (poládicos) exigem justificativas muito especiais - e, portanto, não devem ser usadas de qualquer maneira. Argumentos são difíceis. Eles tomam muito poder conceitual.
Concordo completamente, isso faz muito sentido.
Questão
No entanto, muitas vezes me pego criando uma lista de outra lista e terei que viver com um dos dois males.
Ou eu uso duas linhas no bloco , uma para criar a coisa e outra para adicioná-la ao resultado:
public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
List<Flurp> flurps = new List<Flurp>();
foreach (BadaBoom badaBoom in badaBooms)
{
Flurp flurp = CreateFlurp(badaBoom);
flurps.Add(flurp);
}
return flurps;
}
Ou adiciono um argumento à função da lista à qual a coisa será adicionada, tornando-a "um argumento pior".
public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
List<Flurp> flurps = new List<Flurp>();
foreach (BadaBoom badaBoom in badaBooms)
{
CreateFlurpInList(badaBoom, flurps);
}
return flurps;
}
Questão
Existem (des) vantagens que não estou vendo, o que torna um deles preferível em geral? Ou existem tais vantagens em determinadas situações; Nesse caso, o que devo procurar ao tomar uma decisão?
fonte
flurps.Add(CreateFlurp(badaBoom));
?f(g(x))
é contra o seu guia de estilo, bem, não posso consertá-lo. Quero dizer, você também não se dividesqrt(x*x + y*y)
em quatro linhas, não é? E são três (!) Subexpressões aninhadas em dois (!) Níveis de aninhamento interno (suspiro!). Seu objetivo deve ser legibilidade , não declarações de operador único. Se você quiser mais tarde, bem, eu tenho a linguagem perfeita para você: Assembler.mov
instruções x86 e uma únicajmp toStart
no final. Alguém realmente fez um compilador que faz exatamente isso: Drlwimi
instruções infames sobre o PPC. (Significa Rotate Insert Left Immediate Mask Insert.) Esse comando levou pelo menos cinco operandos (dois registros e três valores imediatos) e executou as seguintes operações: Um conteúdo de um registro foi rotacionado por uma mudança imediata, uma máscara foi criado com uma única execução de 1 bits que foi controlada pelos outros dois operandos imediatos, e os bits que correspondiam a 1 bits nessa máscara no outro operando de registro foram substituídos pelos bits correspondentes do registro girado. Instrução muito bacana :-)Respostas:
Essas diretrizes são uma bússola, não um mapa. Eles apontam você em uma direção sensata . Mas eles não podem realmente dizer em termos absolutos qual é a melhor solução. Em algum momento, você precisa parar de caminhar na direção em que a bússola está apontando, porque chegou ao seu destino.
O Clean Code incentiva você a dividir seu código em blocos muito pequenos e óbvios. Essa é uma direção geralmente boa. Mas quando levado ao extremo (como sugere uma interpretação literal dos conselhos citados), você subdividirá seu código em pedaços inúteis. Nada realmente faz nada, tudo apenas delega. Este é essencialmente outro tipo de ofuscação de código.
É seu trabalho equilibrar “menor é melhor” e “pequeno demais é inútil”. Pergunte a si mesmo qual solução é mais simples. Para mim, essa é claramente a primeira solução, pois obviamente monta uma lista. Este é um idioma bem compreendido. É possível entender esse código sem precisar olhar para outra função.
Se é possível fazer melhor, é notando que “transformar todos os elementos de uma lista para outra lista” é um padrão comum que geralmente pode ser abstraído, usando uma
map()
operação funcional . Em C #, acho que é chamadoSelect
. Algo assim:fonte
CreateFlurps(someList)
quando o BCL já fornecesomeList.ConvertAll(CreateFlurp)
?BadaBoom => CreateFlurp(badaBoom)
é redundante; você pode passarCreateFlurp
diretamente como a função (Select(CreateFlurp)
). (Tanto quanto eu sei, este tem sido sempre o caso.)CreateFlurps
é realmente mais enganoso e mais difícil de entender do que apenas verbadaBooms.Select(CreateFlurp)
. O último é completamente declarativo - não há problema em se decompor e, portanto, não há necessidade de um método.badaBooms.Select(CreateFlurp)
. Você cria um método para que seu nome (alto nível) represente sua implementação (baixo nível). Nesse caso, eles estão no mesmo nível; portanto, para descobrir exatamente o que está acontecendo, preciso apenas olhar o método (em vez de vê-lo na linha).CreateFlurps(badaBooms)
pode conter surpresas, masbadaBooms.Select(CreateFlurp)
não pode. Também é enganoso, porque está pedindo erroneamente um emList
vez de umIEnumerable
.Não! O número ideal de argumentos para uma função é um. Se for zero, você está garantindo que a função precise acessar informações externas para poder executar uma ação. "Tio" Bob entendeu isso muito errado.
Em relação ao seu código, seu primeiro exemplo possui apenas duas linhas no bloco porque você está criando uma variável local na primeira linha. Remova essa atribuição e cumpra estas diretrizes de código limpo:
Mas esse código é muito longo (C #). Apenas faça como:
fonte
O conselho do 'Código Limpo' está completamente errado.
Use duas ou mais linhas no seu loop. Ocultar as mesmas duas linhas em uma função faz sentido quando são algumas matemáticas aleatórias que precisam de uma descrição, mas não fazem nada quando as linhas já são descritivas. 'Criar' e 'Adicionar'
O segundo método mencionado não faz muito sentido, pois você não é obrigado a adicionar um segundo argumento para evitar as duas linhas.
ou
Conforme observado por outros, o conselho de que a melhor função é aquela sem argumentos é inclinado para OOP na melhor das hipóteses e, na pior das hipóteses, conselhos ruins na pior das hipóteses.
fonte
O segundo é definitivamente pior, pois
CreateFlurpInList
aceita a lista e modifica essa lista, tornando a função não pura e difícil de raciocinar. Nada no nome do método sugere que o método seja adicionado apenas à lista.E ofereço a terceira, melhor opção:
E, diabos, você pode incorporar esse método imediatamente se houver apenas um lugar onde ele é usado, pois o one-liner é claro por si só, portanto não precisa ser encapsulado pelo método para dar sentido.
fonte
A versão de um argumento é melhor, mas não principalmente devido ao número de argumentos.
O motivo mais importante é que ele tem um acoplamento mais baixo , o que o torna mais útil, mais fácil de raciocinar, mais fácil de testar e menos provável de se transformar em clones copiados + copiados.
Se você me fornecer uma
CreateFlurp(BadaBoom)
, eu posso usar isso com qualquer tipo de recipiente de coleta: SimpleFlurp[]
,List<Flurp>
,LinkedList<Flurp>
,Dictionary<Key, Flurp>
, e assim por diante. Mas com aCreateFlurpInList(BadaBoom, List<Flurp>)
, eu voltarei para você amanhã pedindo paraCreateFlurpInBindingList(BadaBoom, BindingList<Flurp>)
que meu viewmodel possa receber a notificação de que a lista foi alterada. Que nojo!Como um benefício adicional, é mais provável que a assinatura mais simples se ajuste às APIs existentes. Você diz que tem um problema recorrente
É apenas uma questão de usar as ferramentas disponíveis. A versão mais curta, mais eficiente e melhor é:
Esse código não é apenas para você escrever e testar, mas também é mais rápido, porque
List<T>.ConvertAll()
é inteligente o suficiente para saber que o resultado terá o mesmo número de itens que a entrada e pré-aloca a lista de resultados para o tamanho correto. Enquanto seu código (ambas as versões) exigia o aumento da lista.fonte
List.ConvertAll
. A maneira idiomática de mapear um enumerável de objetos para diferentes objetos em C # é chamadaSelect
. O único motivo queConvertAll
está disponível aqui é porque o OP está pedindo erroneamente umList
no método - deve ser umIEnumerable
.Lembre-se do objetivo geral: facilitar a leitura e a manutenção do código.
Freqüentemente, será possível agrupar várias linhas em uma única função significativa. Faça isso nesses casos. Ocasionalmente, você precisará reconsiderar sua abordagem geral.
Por exemplo, no seu caso, substituindo toda a implementação por var
pode ser uma possibilidade. Ou você pode fazer algo como
Às vezes, a solução mais limpa e legível simplesmente não se encaixa em uma linha. Então você terá duas linhas. Não torne o código mais difícil de entender, apenas para cumprir alguma regra arbitrária.
Seu segundo exemplo é (na minha opinião) consideravelmente mais difícil de entender do que o primeiro. Não é apenas que você tenha um segundo parâmetro, é que o parâmetro é modificado pela função. Veja o que o Clean Code tem a dizer sobre isso. (Não tenha o livro em mãos agora, mas tenho certeza de que é basicamente "não faça isso se puder evitá-lo").
fonte