Discussões de simplicidade

8

Recentemente, na minha empresa, tivemos um pouco de debate sobre abstração versus simplicidade. Uma escola de pensamento que eu caracterizaria como "SECA e abstração não pode fazer mal" e leva a códigos como este:

def make_foo_binary(binaryName, objFiles, fooLibsToLinkAgainst)
    make_exe_task(binaryName, objFiles.ext('.o'), fooLibsToLinkAgainst)
end

e isto:

class String
    def escape_space
        return self.gsub(' ', '\ ')
    end
end

Meu ponto de vista é que a criação de uma abstração como essa, usada apenas em um lugar, torna o código menos legível, pois você está substituindo uma chamada de função com a qual o leitor está familiarizado (gsub) por outro que eles nunca viram before (escape_space), que eles precisarão ler se quiserem entender como o código realmente funciona. A substituição é essencialmente descrita em inglês ("escape space") e o inglês é notoriamente vago. Por exemplo, sem olhar para a definição, você não sabe se ela escapa de todo o espaço em branco ou apenas do caractere de espaço.

Há muita escrita que canta os louvores de DRY e abstração. Alguém está ciente das fontes que descrevem os limites da abstração? Que cantam louvores e discutem as pragmáticas de manter o código simples?

Edit: Eu posso encontrar textos que incentivam a simplicidade na vida ou na escrita (em inglês), por exemplo, "Simplifique, Simplifique!" ou "A escrita vigorosa de Strunk and White é concisa". Onde é o equivalente para a programação?

Martin C. Martin
fonte
1
Eu recomendo a leitura de "Código Limpo", de Robert C. Martin, se você não tiver. Se você tiver, leia os primeiros capítulos com mais cuidado.
Bruno Schäpper
Eu recomendo assistir o Simple Made Easy para uma discussão aprofundada sobre como escrever código simples.
dan_waterworth
3
Não é um fã de "Código Limpo". Excessivamente dogmático.
Rig

Respostas:

9

Claro: Joel Spolsky (você pode ter ouvido falar dele) diz :

Todas as abstrações não triviais, até certo ponto, são vazadas.

Abstrações falham. Às vezes um pouco, às vezes muito. Há vazamento. As coisas dão errado. Isso acontece em todo o lugar quando você tem abstrações.

Além disso, veja KISS e YAGNI, que Jeff Atwood discute :

Como desenvolvedores, acho que também tendemos a ser otimistas demais na avaliação da generalidade de nossas próprias soluções e, assim, acabamos criando [soluções] elaboradas em torno de coisas que podem não justificar esse nível de complexidade. Para combater esse desejo, sugiro seguir a doutrina YAGNI (você não precisa disso). Crie o que você precisa, refatorando agressivamente à medida que avança; não gaste muito tempo planejando cenários futuros grandiosos e desconhecidos. Um bom software pode evoluir para o que acabará se tornando.

(veja o link para a cotação exata)

Minha interpretação destas citações:

  1. É realmente difícil criar boas abstrações - é muito mais fácil criar coisas ruins.

  2. Se você adicionar uma abstração para a qual não há necessidade ou está errada, agora você tem um código extra que precisa ser testado, depurado e mantido. E se você precisar voltar e refatorar, agora terá mais peso morto arrastando-o para baixo.


fonte
1
Boas fontes. +1
KChaloux
7

Não estou familiarizado com o idioma ou o tempo de execução dos seus exemplos, mas gsubnão significa nada para mim. No entanto, escape_spaceé muito mais significativo. Discordo completamente do seu argumento de que chamadas de funções familiares não devem ser substituídas por chamadas para funções que nunca haviam visto antes . Se eu levasse esse argumento ao extremo, nunca se deve adicionar uma nova função a um programa: ele sempre abstrai as chamadas de funções familiares e sempre as substitui por essa nova chamada desconhecida definida pelo usuário.

Um desenvolvedor nunca deve ter que ler o código para entender como ele funciona. Antes de tudo, ele ou ela não deve se preocupar com o funcionamento, mas deve entender como usá-lo e quais efeitos ela tem. Se não for esse o caso, existe um problema com o nome e a assinatura da função ou sua documentação.

Para mim, o objetivo do resumo é remover detalhes internos e fornecer uma interface mais limpa para algumas funcionalidades. No futuro, o funcionamento interno da escape_spacefunção poderá ser alterado ou substituído. Não ligo para que alguma função gsubseja chamada com dois argumentos. Eu me importo que meus espaços sejam escapados. Então isso torna a abstração útil.

No meu idioma favorito, C #, eu sempre adiciono documentação a todas as minhas funções (inclusive privadas), descrevendo coisas como a função, uso, unidades usadas, exceções lançadas, contrato de entrada e tipos. Em seguida, com o IntelliSense do Visual Studio, qualquer desenvolvedor pode ver mais claramente o que a função faz sem ler o código . Sem uma ferramenta como o IntelliSense, os desenvolvedores terão que ser mais específicos em seus nomes de função ou manter a documentação adicional em algum lugar facilmente acessível.

Não acho que as abstrações devam ser limitadas e não conheço essa fonte.

Daniel AA Pelsmaeker
fonte
2
A linguagem que ele está usando é Ruby, e gsub é uma função muito usada na classe String. Você teria dificuldade em encontrar alguém que escrevesse mais do que um pouco de Ruby que não soubesse imediatamente que o gsub é usado como "encontre e substitua tudo".
KChaloux
@KChaloux Também é possível adivinhar para usuários que não são Ruby: além do contexto (os argumentos passados), ele se parece com 'g' + "substitute" - e nos comandos substitutos (por exemplo, no in sede vim :s), 'g' é usado como "todos"
Izkata 9/08/2012
Sim, meu palpite era que ele substituía algo. Mas não gosto de adivinhar o que uma função faz. Deve ser um artefato de tempos anteriores, mas os nomes de funções gosto gsub, atoie wcstombssão muito piores do que o nome escape_space.
Daniel Daniel Pelsmaeker
2
@Virtlink Por outro lado, gsub é um comando comum. Quando você vê, "something".gsub(' ', '\ ')sabe exatamente qual é o resultado que está obtendo. Escapar de espaços não é algo que vejo com muita frequência. Está usando o caractere `\` ou algum outro caractere de escape? Escapa de novas linhas? Sem mencionar que é um monkeypatch da classe String padrão, que é considerada uma prática ruim por certos desenvolvedores.
KChaloux
5

Ao falar sobre abstrações, acho importante definir dois termos: complexidade inerente e incidental. Complexidade inerente é a complexidade do problema que a abstração está resolvendo, complexidade incidental é a complexidade que a abstração está ocultando.

Boas abstrações são aquelas que escondem muita complexidade incidental e tentam resolver os problemas certos, diminuindo sua complexidade inerente. Eu acho que o que você está ficando frustrado aqui são abstrações muito superficiais; eles não escondem muita complexidade incidental e é tão difícil (se não mais) entender as abstrações quanto entender suas implementações.

dan_waterworth
fonte
2

Minha resposta vai se afastar mais da experiência pessoal, mas uma pesquisa rápida encontrou este blog , que parece entrar em muitos detalhes sobre quando a abstração vai longe demais (não apenas a entrada vinculada, mas existem várias relacionadas).

Basicamente, a abstração pode ser uma coisa boa. No entanto, como qualquer outra coisa, pode-se deixar levar. Seus exemplos são bons, onde a abstração foi longe demais. Você está criando "repasses" que basicamente nada fazem além de renomear a função em questão.

Agora, no caso de funções personalizadas, isso pode ser necessário, se você deseja descontinuar um esquema de nomeação antigo em favor de um novo (como alterar escape_space para escapeSpace ou escWhitespace, ou o que for). Para funções de biblioteca, no entanto? Exagero. Sem mencionar contraproducente. O que você ganhou ao fazer essa abstração? Se isso é tudo o que está fazendo, então tudo o que você ganhou é a dor de cabeça de ter que lembrar que a função e passar esse conhecimento para alguém (que, com funções de biblioteca, os desenvolvedores são ou já está ciente deles, ou deveria ser capaz de escrever ruby gsubem Google e descubra o que isso significa).

Eu recomendo ser mais inteligente ao determinar quando abstrair algo. Por exemplo, coisas com mais de 2 a 3 linhas e usadas pelo menos duas vezes (mesmo que haja pequenas diferenças, muitas vezes, essas diferenças podem ser parametrizadas), geralmente são boas candidatas à abstração. Às vezes, considerarei grandes blocos de código que fazem uma coisa específica em uma função maior, para facilitar a leitura e o princípio "trabalho único", e abstraí-los para funções protegidas dentro da mesma classe (geralmente no imediato vizinhança da função que a utiliza).

Além disso, não esqueça o YAGNI (você não precisará dele) e a otimização prematura. Isso está sendo usado atualmente mais de uma vez? Não? Então não se preocupe em abstraí-lo agora (a menos que isso crie um ganho substancial de legibilidade). "Mas podemos precisar disso mais tarde!" Depois, abstraia-o quando você realmente precisar dele em outro lugar. Até lá, você acaba com ponteiros para ponteiros, sem nenhum benefício real.

Do outro lado desta moeda, quando você abstrair algo, faça-o assim que a necessidade se tornar evidente. É mais fácil mover e ajustar dois ou três blocos de código do que 15. De um modo geral, assim que me vejo copiando blocos de código de outro lugar ou escrevendo algo que parece muito familiar, começo a pensar em como abstraí-lo.

Shauna
fonte
2
Alguns anos atrás, eu concordei totalmente com você e trabalhei da maneira que você a descreve. Nesse meio tempo, li algumas coisas de Robert Martin e comecei a repensar minha maneira de programar. Construir funções muito pequenas com bons nomes, mesmo quando a função é usada uma vez, tem uma vantagem real: você precisa de muito menos comentários. E ajuda a manter os chamadores dessa função mais simples e legíveis. Definitivamente, isso não é "otimização prematura" (Knuth estava falando sobre desempenho, não sobre qualidade de código). O YAGNI deve ser considerado, é claro, mas boas ferramentas de refatoração facilitaram muito essas mudanças hoje.
Doc Brown
1
@ DocBrown - Acho que podemos concordar mais do que você pensa. Provavelmente poderíamos conversar por horas ou dias sobre os prós e os contras da abstração. Eu estava tentando ser mais conciso do que isso, mas toquei na sua grande preocupação quando mencionei a legibilidade e o princípio "um emprego" / responsabilidade única. Onde geralmente traço a linha é quando uma função está simplesmente atuando como passagem para outra função (que, para mim, na maioria dos casos, é abstração por causa da abstração).
Shauna
2

Primeiro, dê uma olhada neste exemplo de Robert C. Martin:

http://blog.objectmentor.com/articles/2009/09/11/one-thing-extract-till-you-drop

A razão para esse tipo de refatoração é conseqüentemente criar abstrações para criar funções muito pequenas, de modo que cada função faça apenas uma coisa . Isso leva a funções como as que você nos mostrou acima. Eu acredito que isso é uma coisa boa - ajuda você a criar código onde cada função chamada dentro de outra função está no mesmo nível de abstração.

Se você acha que usar nomes como make_foo_binaryou escape_spacetornar o código menos legível do que como consequência, tente escolher nomes melhores (por exemplo escape_space_chars_with_backslash:) em vez de abandonar a abstração.

Doc Brown
fonte