Ultimamente, refatorei algum código no trabalho e achei que fiz um bom trabalho. Deixei cair 980 linhas de código para 450 e reduzi pela metade o número de classes.
Ao mostrar isso aos meus colegas, alguns não concordaram que isso fosse uma melhoria.
Eles disseram - "menos linhas de código não são necessariamente melhores"
Percebo que pode haver casos extremos em que as pessoas escrevem linhas realmente longas e / ou colocam tudo em um único método para salvar algumas linhas, mas não foi o que fiz. Na minha opinião, o código é bem estruturado e mais simples de compreender / manter, por ter metade do tamanho.
Estou lutando para ver por que alguém iria querer trabalhar com o dobro do código necessário para realizar um trabalho, e estou imaginando se alguém se sente da mesma forma que meus colegas e pode fazer bons casos por ter mais código por menos ?
fonte
Respostas:
Uma pessoa magra não é necessariamente mais saudável que uma pessoa com sobrepeso.
Uma história infantil de 980 linhas é mais fácil de ler do que uma tese de 450 linhas de física.
Existem muitos atributos que determinam a qualidade do seu código. Alguns são simplesmente computados, como Complexidade Ciclomática e Complexidade de Halstead . Outros são definidos de maneira mais vaga, como coesão , legibilidade, compreensibilidade, extensibilidade, robustez, correção, auto-documentação, limpeza, testabilidade e muito mais.
Pode ser, por exemplo, que, enquanto você reduzia o tamanho geral do código - você introduzia uma complexidade injustificada adicional e tornava o código mais enigmático.
Dividir um longo pedaço de código em métodos minúsculos pode ser tão prejudicial quanto benéfico .
Peça a seus colegas que forneçam feedback específico sobre o motivo pelo qual eles acham que seus esforços de refatoração produziram um resultado indesejável.
fonte
Curiosamente, um colega e eu atualmente estamos no meio de um refator que aumentará o número de classes e funções em pouco menos do que o dobro, embora as linhas de código permaneçam iguais. Por isso, tenho um bom exemplo.
No nosso caso, tínhamos uma camada de abstração que realmente deveria ter sido duas. Tudo estava amontoado na camada da interface do usuário. Ao dividi-lo em duas camadas, tudo se torna mais coeso, e testar e manter as peças individuais se torna muito mais simples.
Não é o tamanho do código que está incomodando seus colegas, é outra coisa. Se eles não conseguirem articulá-lo, tente olhar o código como se nunca tivesse visto a implementação antiga e avaliá-lo por seus próprios méritos, e não apenas em comparação. Às vezes, quando faço uma refatoração longa, meio que perco de vista o objetivo original e levo as coisas longe demais. Dê uma olhada crítica no "quadro geral" e coloque-a de volta nos trilhos, talvez com a ajuda de um programador de pares, cujo conselho você valoriza.
fonte
Uma citação, muitas vezes atribuída a Albert Einstein, vem à mente:
Quando você exagera no corte de itens, pode tornar o código mais difícil de ler. Como "fácil / difícil de ler" pode ser um termo muito subjetivo, explicarei exatamente o que quero dizer com isso: uma medida do grau de dificuldade que um desenvolvedor experiente terá ao determinar "o que esse código faz?" apenas olhando a fonte, sem o auxílio de ferramentas especializadas.
Idiomas como Java e Pascal são famosos por sua verbosidade. As pessoas frequentemente apontam para certos elementos sintáticos e dizem ironicamente que "elas estão lá apenas para facilitar o trabalho do compilador". Isso é mais ou menos verdade, exceto a parte "justa". Quanto mais informações explícitas houver, mais fácil será a leitura e a compreensão do código, não apenas por um compilador, mas também por um ser humano.
Se eu digo
var x = 2 + 2;
, é imediatamente óbvio quex
é suposto ser um número inteiro. Mas se eu disservar foo = value.Response;
, é bem menos claro o quefoo
representa ou quais são suas propriedades e capacidades. Mesmo que o compilador possa inferir facilmente, ele coloca muito mais esforço cognitivo em uma pessoa.Lembre-se de que os programas devem ser escritos para que as pessoas leiam e, incidentalmente, para as máquinas executarem. (Ironicamente, essa citação vem de um livro didático dedicado a um idioma famoso por ser extremamente difícil de ler!) É uma boa idéia remover coisas que são redundantes, mas não retire um código que facilite o contato com os seres humanos. descobrir o que está acontecendo, mesmo que não seja estritamente necessário para a gravação do programa.
fonte
var
exemplo não é particularmente bom de simplificação, porque na maioria das vezes a leitura e a compreensão do código envolvem a compreensão do comportamento em um determinado nível de abstração; portanto, conhecer os tipos reais de variáveis específicas normalmente não muda nada (apenas ajuda a entender abstrações mais baixas). Um exemplo melhor seria várias linhas de código simples compactadas em uma única instrução complicada - por exemplo,if ((x = Foo()) != (y = Bar()) && CheckResult(x, y))
leva tempo para grudar e conhecer os tipos dex
ouy
não ajuda nem um pouco.Um código mais longo pode ser mais fácil de ler. Geralmente é o oposto, mas há muitas exceções - algumas delas descritas em outras respostas.
Mas vamos olhar de um ângulo diferente. Assumimos que o novo código será considerado superior pela maioria dos programadores qualificados que veem os dois trechos de código sem ter conhecimento adicional da cultura, da base de códigos ou do roteiro da empresa. Mesmo assim, há muitos motivos para contestar o novo código. Por uma questão de brevidade, chamarei "Pessoas que criticam o novo código" Pecritenc :
fonte
Que tipo de código é melhor pode depender da experiência dos programadores e também das ferramentas que eles usam. Por exemplo, eis por que o que normalmente seria considerado código mal escrito pode ser mais eficaz em algumas situações do que o código orientado a objetos bem escrito que faz uso total da herança:
(1) Alguns programadores simplesmente não têm uma compreensão intuitiva da programação orientada a objetos. Se sua metáfora para um projeto de software é um circuito elétrico, você espera muita duplicação de código. Você gostaria de ver mais ou menos os mesmos métodos em muitas classes. Eles vão fazer você se sentir em casa. E um projeto em que você precisa procurar métodos nas classes principais ou mesmo nas classes dos avós para ver o que está acontecendo pode parecer hostil. Você não quer entender como a classe pai funciona e depois entender como a classe atual difere. Você quer entender diretamente como a classe atual funciona e acha confuso o fato de que as informações estão espalhadas por vários arquivos.
Além disso, quando você apenas deseja corrigir um problema específico em uma classe específica, pode não gostar de pensar em corrigir o problema diretamente na classe base ou sobrescrever o método na sua classe de interesse atual. (Sem herança, você não precisaria tomar uma decisão consciente. O padrão é ignorar problemas semelhantes em classes semelhantes até que sejam relatados como bugs.) Esse último aspecto não é realmente um argumento válido, embora possa explicar algumas das oposição.
(2) Alguns programadores usam muito o depurador. Embora em geral eu esteja firmemente do lado da herança de código e impeça a duplicação, compartilho parte da frustração que descrevi em (1) ao depurar código orientado a objetos. Quando você segue a execução do código, às vezes ele continua pulando entre as classes (ancestrais), mesmo que permaneça no mesmo objeto. Além disso, ao definir um ponto de interrupção em um código bem escrito, é mais provável que ele seja acionado quando não for útil; portanto, você pode ter que se esforçar para torná-lo condicional (quando possível) ou até mesmo continuar manualmente muitas vezes antes do acionador relevante.
fonte
Depende totalmente. Eu tenho trabalhado em um projeto que não permite variáveis booleanas como parâmetros de função, mas requer um dedicado
enum
para cada opção.Assim,
é muito mais detalhado do que
Contudo,
é muito mais legível do que
O compilador deve gerar o mesmo código para ambos, portanto, não há nada a ser ganho usando o formato mais curto.
fonte
Eu diria que a coesão pode ser um problema.
Por exemplo, em um aplicativo da web, digamos que você tenha uma página de administrador na qual você indexa todos os produtos, que é essencialmente o mesmo código (índice) que você usaria em uma situação de página inicial, para .. apenas indexar os produtos.
Se você decidir parcializar tudo para ficar seco e elegante, precisará adicionar muitas condições para saber se a navegação do usuário é ou não um administrador e desordenar o código com coisas desnecessárias, o que o tornará altamente ilegível, digamos um designer!
Portanto, em uma situação como essa, mesmo que o código seja praticamente o mesmo, apenas porque ele pode ser dimensionado para outra coisa e os casos de uso podem mudar levemente, seria ruim ir atrás de cada um deles adicionando condições e ifs. Portanto, uma boa estratégia seria abandonar o conceito DRY e dividir o código em peças de manutenção.
fonte
Quando menos código é menos sustentável / extensível que mais código. A refatoração para concisão do código geralmente remove construções de código "desnecessárias" no interesse do LoC. O problema é que essas construções de código, como declarações de interface paralela, métodos / subclasses extraídos, etc, são necessárias, caso esse código precise fazer mais do que atualmente ou de forma diferente. No extremo, certas soluções personalizadas para o problema específico podem não funcionar se a definição do problema mudar um pouco.
Um exemplo; você tem uma lista de números inteiros. Cada um desses números inteiros possui um valor duplicado na lista, exceto um. Seu algoritmo deve encontrar esse valor não emparelhado. A solução de caso geral é comparar todos os números com todos os outros números até encontrar um número que não tem dupe na lista, que é uma operação de tempo N ^ 2. Você também pode criar um histograma usando uma hashtable, mas isso é muito ineficiente em espaço. No entanto, você pode torná-lo em tempo linear e espaço constante usando uma operação XOR bit a bit; XOR todo número inteiro em relação a um "total" em execução (começando com zero) e, no final, a soma em execução será o valor do seu número inteiro não emparelhado. Muito elegante. Até que os requisitos sejam alterados e mais de um número na lista possa não ser emparelhado, ou os números inteiros incluem zero. Agora, seu programa retorna lixo ou resultados ambíguos (se retornar zero, isso significa que todos os elementos estão emparelhados ou que o elemento não emparelhado é zero?). Esse é o problema de implementações "inteligentes" na programação do mundo real.
fonte
O código do computador precisa fazer várias coisas. Um código "minimalista" que não faz essas coisas não é um bom código.
Por exemplo, um programa de computador deve cobrir todos os casos possíveis (ou no mínimo, todos os casos prováveis). Se um trecho de código cobre apenas um "caso base" e ignora outros, não é um bom código, mesmo que breve.
O código do computador deve ser "escalável". Um código enigmático pode funcionar para apenas um aplicativo especializado, enquanto um programa mais longo, porém mais aberto, pode facilitar a adição de novos aplicativos.
O código do computador deve ficar claro. Como outro respondedor demonstrou, é possível que um codificador de núcleo duro produza uma função do tipo "algorítmica" de uma linha que faz o trabalho. Mas o one-liner teve que ser dividido em cinco "sentenças" diferentes antes de ficar claro para o programador médio.
fonte
Desempenho computacional. Ao otimizar o alinhamento de tubos ou as partes em execução do seu código em paralelo, pode ser benéfico, por exemplo, não fazer um loop de 1 a 400, mas de 1 a 50 e colocar 8 instâncias de código semelhante em cada loop. Não estou assumindo que esse foi o caso na sua situação, mas é um exemplo em que mais linhas são melhores (em termos de desempenho).
fonte