Em que casos, menos código não é melhor? [fechadas]

55

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 ?

PiersyP
fonte
145
O tamanho do código é medido no tempo em que você precisa lê-lo e entendê-lo, não linhas ou número de caracteres.
Bergi 31/08/19
13
Sua pergunta, como está escrita, é categoricamente muito ampla. Recomende escrever um novo sobre alterações específicas que você fez.
Jpmc26 31/08/19
8
Considere o algoritmo de raiz quadrada inversa rápida . A implementação do método Newton completo com a nomeação adequada de variáveis ​​seria muito mais clara e muito mais fácil de ler, embora provavelmente contenha mais linhas de código. (Observe que, neste caso específico, o uso de código inteligente era justificável por questões de desempenho).
Maciej Piechotka 31/08
65
Existe todo um site de troca de pilhas dedicado a responder sua pergunta: codegolf.stackexchange.com . :)
Federico Poloni
10
Possível duplicata de Em que ponto a brevidade não é mais uma virtude?
Bob Tway

Respostas:

123

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.

MA Hanin
fonte
11
@PiersyP, apenas uma FYI, uma das diretrizes que me ensinaram sobre uma boa refatoração é que devemos ver a complexidade ciclomática reduzida a uma raiz quadrada do que era originalmente.
MA Hanin
4
@PiersyP também, não estou dizendo que seu código é pior ou melhor do que era. Como alguém de fora, eu realmente não posso dizer. Também pode ser que seus colegas sejam conservadores demais e tenham medo de sua alteração simplesmente porque não fizeram o esforço necessário para revisá-la e validá-la. Por isso, sugeri que você solicitasse feedback adicional.
MA Hanin
6
Bom trabalho, pessoal - você estabeleceu que há um peso "certo" em algum lugar (o número exato pode variar). Até as postagens originais do @Neil dizem "excesso de peso", em vez de apenas "a pessoa mais pesada é", e isso ocorre porque existe um ponto ideal, assim como na programação. Adicionar código além desse "tamanho certo" é apenas desorganizado, e remover linhas abaixo desse ponto sacrifica a compreensão por uma questão de brevidade. Saber onde exatamente esse ponto é ... É difícil.
AC
11
Só porque não é necessário, não significa que não tenha valor.
31516 Chris Wohlert
11
@ Neil Você geralmente está certo, mas o "equilíbrio" sempre evasivo que você alude é uma espécie de mito, objetivamente falando. Todo mundo tem uma idéia diferente do que é um "bom equilíbrio". Claramente, o OP achou que ele havia feito algo de bom, e seus colegas de trabalho não, mas tenho certeza de que todos pensaram que tinham o "equilíbrio certo" quando escreveram o código.
code_dredd
35

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.

Karl Bielefeldt
fonte
11
Sim, definitivamente separe a interface do usuário de outras coisas, isso sempre vale a pena. No que diz respeito a perder de vista o objetivo original, concordo um pouco, mas também é possível redesenhar para algo melhor ou a caminho de melhorar. Como o velho argumento sobre a Evolução ("de que serve uma ala?"), As coisas não melhoram se você nunca tiver tempo para melhorá-las. Você nem sempre sabe para onde está indo até o caminho certo. Concordo em tentar descobrir por que os colegas de trabalho se sentem desconfortáveis, mas talvez seja realmente "o problema deles", não o seu.
17

Uma citação, muitas vezes atribuída a Albert Einstein, vem à mente:

Faça tudo da forma mais simples possível, mas não muito simples.

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 que xé suposto ser um número inteiro. Mas se eu disser var foo = value.Response;, é bem menos claro o que foorepresenta 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.

Mason Wheeler
fonte
7
o varexemplo 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 de xou ynão ajuda nem um pouco.
Ben Cottrell
15

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 :

  • Estabilidade. Se se sabia que o código antigo era estável, a estabilidade do novo código é desconhecida. Antes que o novo código possa ser usado, ele ainda precisa ser testado. Se, por algum motivo, o teste adequado não estiver disponível, a mudança será um grande problema. Mesmo se o teste estiver disponível, o Pecritenc pode pensar que o esforço não vale a melhoria (menor) do código.
  • Desempenho / escala. O código antigo pode ter melhorado a escala e o Pecritenc assume que o desempenho se tornará um problema no futuro, à medida que clientes e recursos em breve * se acumulam.
  • Extensibilidade. O código antigo pode ter permitido a introdução fácil de alguns recursos que o Pecritenc supõe que sejam adicionados em breve *.
  • Familiaridade. O código antigo pode ter reutilizado padrões usados ​​em outros 5 locais da base de código da empresa. Ao mesmo tempo, o novo código usa um padrão sofisticado que apenas metade da empresa já ouviu falar nesse momento.
  • Batom em um porco. O Pecritenc pode pensar que tanto o código antigo quanto o novo são inúteis ou irrelevantes, tornando inútil qualquer comparação entre eles.
  • Orgulho. Pecritenc pode ter sido o autor original do código e não gosta de pessoas fazendo grandes mudanças em seu código. Ele pode até ver as melhorias como um insulto leve, porque elas sugerem que ele deveria ter feito melhor.
Peter
fonte
4
+1 para 'Pecritenc' e um resumo muito bom de objeções praticáveis ​​que devem ser pré-consideradas antes da pré-avaliação.
11
E +1 para 'extensibilidade' - eu estava pensando que o código original pode ter funções ou classes destinadas a serem usadas em um projeto futuro; portanto, as abstrações podem parecer redundantes ou desnecessárias, mas apenas no contexto de um único programa.
Darren Ringer
Além disso, o código em questão pode não ser um código crítico, sendo considerado um desperdício de recursos de engenharia para limpá-lo.
Erik Eidt
@nocomprende Algum motivo pelo qual você usou práticas, pré-ponderação e pré-avaliação? Método semelhante ao Pecritenc, talvez?
precisa saber é o seguinte
@MilindR Provavelmente um preconceito, uma predileção ou talvez uma preferência pessoal? Ou, talvez apenas nenhuma razão, uma confluência cósmica de cofatores, confundindo condições conspiratórias. Não faço ideia, realmente. E quanto a você?
1

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
3
"aulas de avós"! haw haw! Apenas observe as aulas de Adão e Eva. (E a classe de Deus, é claro) Antes disso, era sem formas e sem efeito.
1

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 enumpara cada opção.

Assim,

enum OPTION1 { OPTION1_OFF, OPTION1_ON };
enum OPTION2 { OPTION2_OFF, OPTION2_ON };

void doSomething(OPTION1, OPTION2);

é muito mais detalhado do que

void doSomething(bool, bool);

Contudo,

doSomething(OPTION1_ON, OPTION2_OFF);

é muito mais legível do que

doSomething(true, false);

O compilador deve gerar o mesmo código para ambos, portanto, não há nada a ser ganho usando o formato mais curto.

Simon Richter
fonte
0

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.

frcake
fonte
0
  • Quando menos código não faz o mesmo trabalho que mais código. A refatoração para simplificar é boa, mas você deve tomar cuidado para não simplificar demais o espaço do problema que esta solução atende. 980 linhas de código podem lidar com mais casos de canto que 450.
  • Quando menos código não falha tão graciosamente quanto mais código. Eu vi alguns trabalhos de "ref *** toring" realizados no código para remover a tentativa de captura "desnecessária" e outro tratamento de casos de erro. O resultado inevitável foi, em vez de mostrar uma caixa de diálogo com uma boa mensagem sobre o erro e o que o usuário poderia fazer, o aplicativo travou ou YSODed.
  • 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.

  • Quando menos código é menos documentado do que mais código. Ser capaz de ler o próprio código e determinar o que ele está fazendo é fundamental para o desenvolvimento da equipe. Dar um algoritmo de cérebro-f *** que você escreveu que funciona muito bem a um desenvolvedor júnior e pedir para ele ajustá-lo para modificar um pouco a saída não vai levar muito longe. Muitos desenvolvedores seniores também teriam problemas com essa situação. Ser capaz de entender a qualquer momento o que o código está fazendo e o que pode dar errado com ele é a chave para um ambiente de desenvolvimento de equipe de trabalho (e até solo; garanto que o flash da genialidade que você tinha ao escrever um O método on-line para curar o câncer desaparecerá há muito tempo quando você voltar a essa função procurando curá-la também.)
KeithS
fonte
0

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.

Tom Au
fonte
O dever está nos olhos de quem vê.
-2

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).

Hans Janssen
fonte
4
Um bom compilador deve saber melhor que um programador comum como desenrolar loops para uma arquitetura de computador específica, mas o ponto geral é válido. Uma vez, observei o código-fonte para uma rotina de multiplicação de matrizes a partir de uma biblioteca de alto desempenho do Cray. A multiplicação de matrizes é três loops aninhados e cerca de 6 linhas de código no total, certo? Errado - a rotina da biblioteca percorreu cerca de 1100 linhas de código, além de um número semelhante de linhas de comentários, explicando por que era tão longo!
alephzero
11
@alephzero uau, eu adoraria ver esse código, ele deve ser apenas Cray Cray.
@alephzero, bons compiladores podem fazer muito, mas infelizmente nem tudo. O lado positivo é que essas são as coisas que mantêm a programação interessante!
Hans Janssen
2
@alephzero De fato, um bom código de multiplicação de matrizes não economiza um pouco de tempo (ou seja, reduz-o por um fator constante), ele usa um algoritmo totalmente diferente com complexidade assintótica diferente, por exemplo, o algoritmo Strassen é aproximadamente O (n ^ 2.8) em vez de O (n ^ 3).
Arthur Tacca