Ouvi muitas vezes quando outros desenvolvedores usam essa frase para "anunciar" alguns padrões ou desenvolver práticas recomendadas. Na maioria das vezes, essa frase é usada quando você está falando sobre os benefícios da programação funcional.
A frase "Fácil de raciocinar" foi usada como é, sem nenhuma explicação ou amostra de código. Então, para mim, torna-se a próxima palavra "buzz", que mais desenvolvedores "experientes" usam em suas conversas.
Pergunta: Você pode fornecer alguns exemplos de exemplos "Não é fácil de raciocinar", para que possa ser comparado com os exemplos de "Fácil raciocinar"?
Respostas:
A meu ver, a frase "fácil de raciocinar" refere-se a códigos fáceis de "executar na sua cabeça".
Ao olhar para um pedaço de código, se ele é curto, claramente escrito, com bons nomes e mutação mínima de valores, trabalhar mentalmente o que o código faz é uma tarefa (relativamente) fácil.
Um longo pedaço de código com nomes ruins, variáveis que mudam constantemente de valor e ramificações complicadas normalmente exigem, por exemplo, uma caneta e um pedaço de papel para ajudar a acompanhar o estado atual. Portanto, esse código não pode ser facilmente trabalhado apenas na sua cabeça; portanto, não é fácil raciocinar sobre esse código.
fonte
"Code easy to reason about" almost exclusively alludes to its mathematical properties and formal verification
- que soa aproximadamente como uma resposta para a pergunta. Você pode postar isso como uma resposta, em vez de discordar sobre qual é a resposta (subjetiva) nos comentários.É fácil raciocinar sobre um mecanismo ou parte do código quando você precisa levar em consideração algumas coisas para prever o que fará e as coisas que você precisa levar em consideração estão facilmente disponíveis.
Funções verdadeiras, sem efeitos colaterais e sem estado, são fáceis de raciocinar, porque a saída é completamente determinada pela entrada, que está exatamente nos parâmetros.
Por outro lado, é muito mais difícil argumentar sobre um objeto com estado, porque é necessário levar em consideração em que estado o objeto se encontra quando um método é chamado, o que significa que é necessário pensar em quais outras situações poderiam levar o objeto a estar em um estado. estado particular.
Piores ainda são as variáveis globais: para raciocinar sobre o código que lê uma variável global, você precisa entender onde essa variável pode ser configurada e por que - e talvez nem seja fácil encontrar todos esses lugares.
A coisa mais difícil de se raciocinar é a programação multithread com estado compartilhado, porque não apenas você tem estado, mas vários threads alterando-o ao mesmo tempo, para raciocinar sobre o que um pedaço de código faz quando executado por um thread que você é preciso permitir a possibilidade de que, em cada ponto de execução, algum outro encadeamento (ou vários deles!) possa estar executando praticamente qualquer outra parte do código e altere os dados nos quais você está operando sob seus olhos. Em teoria, isso pode ser gerenciado com mutexes / monitores / seções críticas / o que você chamar, mas na prática nenhum humano é capaz de fazer isso de maneira confiável, a menos que confinem drasticamente o estado compartilhado e / ou o paralelismo a muito pequeno seções do código.
fonte
make
especialização de modelo C ++ e sobrecarga de função) podem colocá-lo de volta na posição de considerar todo o programa. Mesmo quando você acha que encontrou a definição de algo, o idioma permite que uma declaração mais específica em qualquer lugar do programa a substitua. Seu IDE pode ajudar com isso.sealed
não ser o padrão?No caso de programação funcional, o significado de "Fácil de raciocinar" é principalmente que é determinístico. Com isso, quis dizer que uma determinada entrada sempre levará à mesma saída. Você pode fazer o que quiser com o programa, desde que não toque nesse trecho de código, ele não será quebrado.
Por outro lado, o OO normalmente é mais difícil de raciocinar, porque a "saída" produzida depende do estado interno de cada objeto envolvido. A maneira típica de manifestar são efeitos colaterais inesperados : ao alterar uma parte do código, uma parte aparentemente não relacionada é interrompida.
... a desvantagem da programação funcional é que, na prática, muito do que você deseja fazer é IO e gerenciamento de estado.
No entanto, existem muitas outras coisas que são mais difíceis de raciocinar, e eu concordo com @Kilian que a simultaneidade é um excelente exemplo. Sistemas distribuídos também.
fonte
Evitando discussões mais amplas e abordando a questão específica:
Refiro-lhe "A história de Mel, um verdadeiro programador" , um pedaço do folclore de programadores que data de 1983 e, portanto, conta como "lenda" para nossa profissão.
Ele conta a história de um programador escrevendo código que preferia técnicas misteriosas sempre que possível, incluindo código auto-referencial e auto-modificador e exploração deliberada de erros de máquina:
Este é um exemplo de código "difícil de raciocinar".
Claro, Mel discordaria ...
fonte
Eu posso fornecer um exemplo, e muito comum.
Considere o seguinte código C #.
Agora considere esta alternativa.
No segundo exemplo, eu sei exatamente o que esse código está fazendo rapidamente. Quando vejo
Select
, sei que uma lista de itens está sendo convertida em uma lista de outra coisa. Quando vejoWhere
, sei que certos itens estão sendo filtrados. De relance, consigo entender o quenames
é e fazer uso efetivo dele.Quando vejo um
for
loop, não faço ideia do que está acontecendo até ler o código. E, às vezes, tenho que rastreá-lo para ter certeza de que sou responsável por todos os efeitos colaterais. Eu tenho que fazer um pouco de trabalho para entender o que são nomes (além da definição de tipo) e como usá-lo efetivamente. Assim, o primeiro exemplo é mais difícil de raciocinar do que o segundo.Por fim, ser fácil raciocinar aqui também depende da compreensão dos métodos
Select
e LINQWhere
. Se você não os conhece, é mais difícil pensar no segundo código inicialmente. Mas você paga apenas o custo para entendê-los uma vez. Você paga o custo para entender umfor
loop toda vez que usa um e novamente toda vez que ele muda. Às vezes, vale a pena pagar o custo, mas geralmente ser "mais fácil de raciocinar" é muito mais importante.fonte
Uma frase relacionada é (paráfrase),
Um exemplo de relativamente "fácil de raciocinar" pode ser o RAII .
Outro exemplo pode ser evitar o abraço mortal : se você pode segurar uma fechadura e adquirir outra fechadura, e há muitas fechaduras, é difícil ter certeza de que não há cenário em que o abraço mortal possa ocorrer. A adição de uma regra como "existe apenas um bloqueio (global)" ou "você não tem permissão para adquirir um segundo bloqueio enquanto mantém um primeiro bloqueio" torna o sistema relativamente fácil de raciocinar.
fonte
CComPtr<>
) com função em estilo C (CoUninitialize()
). Também acho um exemplo bizarro, desde que me lembro de você invocar CoInitialize / CoUninitialize no escopo do módulo e por toda a vida útil do módulo, por exemplo, dentromain
ou dentroDllMain
, e não em algum escopo de função local de vida curta, como mostrado no exemplo .main
função de ponto de entrada ( ) para um aplicativo. Você inicializa o COM na inicialização e o inicializa logo antes de sair. Exceto que você possui objetos globais, como ponteiros inteligentes COM, usando o paradigma RAII. Em relação à mistura de estilos: um objeto global que inicializou o COM no seu ctor e não inicializou no seu dtor é viável, e o que Raymond sugere, mas é sutil e não é fácil de raciocinar.O cerne da programação é a análise de casos. Alan Perlis comentou isso no Epigram # 32: Os programadores não devem ser medidos por sua engenhosidade e lógica, mas pela integridade de sua análise de caso.
É fácil argumentar sobre uma situação se a análise de caso é fácil. Isso significa que existem poucos casos a serem considerados ou, na sua falta, poucos casos especiais - pode haver grandes espaços de casos, mas que colapsam devido a algumas regularidades ou sucumbem a uma técnica de raciocínio, como a indução.
Uma versão recursiva de um algoritmo, por exemplo, geralmente é mais fácil de raciocinar do que uma versão imperativa, porque não contribui com casos supérfluos que surgem pela mutação de variáveis de estado de suporte que não aparecem na versão recursiva. Além disso, a estrutura da recursão é tal que se encaixa em um padrão matemático de prova por indução. Não precisamos considerar complexidades como variantes de loop e pré-condições estritas mais fracas e outros enfeites.
Outro aspecto disso é a estrutura do espaço do caso. É mais fácil argumentar sobre uma situação que possui uma divisão plana ou quase plana em casos, em comparação com uma situação hierárquica de caso: casos com sub e sub-casos e sub-casos e assim por diante.
Uma propriedade de sistemas que simplifica o raciocínio é a ortogonalidade : é a propriedade de que os casos que governam os subsistemas permanecem independentes quando esses subsistemas são combinados. Nenhuma combinação dá origem a "casos especiais". Se algo de quatro casos é combinado com algo de três casos ortogonalmente, há doze casos, mas idealmentecada caso é uma combinação de dois casos que permanecem independentes. Em certo sentido, não existem realmente doze casos; as combinações são apenas "fenômenos emergentes do tipo caso" com os quais não precisamos nos preocupar. O que isso significa é que ainda temos quatro casos em que podemos pensar sem considerar os outros três no outro subsistema e vice-versa. Se algumas das combinações precisam ser especialmente identificadas e dotadas de lógica adicional, o raciocínio é mais difícil. Na pior das hipóteses, todas as combinações têm um tratamento especial e, na verdade, existem doze novos casos, além dos quatro e três originais.
fonte
Certo. Tome simultaneidade:
Seções críticas impostas por mutexes: fáceis de entender porque existe apenas um princípio (dois segmentos de execução não podem entrar na seção crítica simultaneamente), mas propensos a ineficiência e impasse.
Modelos alternativos, como programação ou atores sem bloqueios: potencialmente muito mais elegantes e poderosos, mas terrivelmente difíceis de entender, porque você não pode mais confiar em (aparentemente) conceitos fundamentais como "agora escreva esse valor nesse local".
Ser fácil de raciocinar é um aspecto de um método. Mas escolher qual método usar requer considerar todos os aspectos em combinação.
fonte
Vamos limitar a tarefa ao raciocínio formal. Porque o raciocínio humorístico, inventivo ou poético tem leis diferentes.
Mesmo assim, a expressão é pouco definida e não pode ser definida de maneira estrita. Mas isso não significa que deva permanecer tão obscuro para nós. Vamos imaginar que uma estrutura esteja passando em algum teste e recebendo notas para diferentes pontos. As boas notas para TODOS os pontos significam que a estrutura é conveniente em todos os aspectos e, portanto, "Fácil de raciocinar".
A estrutura "Fácil de raciocinar" deve receber boas notas para o seguinte:
O teste é subjetivo? Sim, naturalmente é. Mas a expressão em si também é subjetiva. O que é fácil para uma pessoa, não é fácil para outra. Portanto, os testes devem ser diferentes para os diferentes domínios.
fonte
A idéia de que as linguagens funcionais são possíveis de raciocinar vem de sua história, especificamente o ML, que foi desenvolvido como uma linguagem de programação análoga às construções que a lógica para funções computáveis usava para raciocinar. A maioria das linguagens funcionais está mais próxima dos cálculos formais de programação do que as imperativas; portanto, a tradução do código para a entrada de um sistema de sistema de raciocínio é menos onerosa.
Para um exemplo de um sistema de raciocínio, no cálculo pi, cada local de memória mutável em uma linguagem imperativa precisa ser representado como um processo paralelo separado, enquanto uma sequência de operações funcionais é um processo único. Quarenta anos depois do provador do teorema de LFC, estamos trabalhando com GB de RAM, portanto, ter centenas de processos é menos um problema - usei o pi-calculus para remover possíveis bloqueios de algumas centenas de linhas de C ++, apesar da representação ter centenas de processa o raciocínio esgotar o espaço de estado em cerca de 3 GB e curar um bug intermitente. Isso teria sido impossível nos anos 70 ou exigido um supercomputador no início dos anos 90, enquanto o espaço de estados de um programa de linguagem funcional de tamanho semelhante era pequeno o suficiente para se pensar naquela época.
A partir das outras respostas, a frase está se tornando uma frase de efeito, embora muitas das dificuldades que dificultam a argumentação sobre linguagens imperativas sejam corroídas pela lei de Moore.
fonte
Fácil de raciocinar é um termo culturalmente específico, e é por isso que é tão difícil apresentar exemplos concretos. É um termo ancorado às pessoas que devem raciocinar.
"Fácil de raciocinar" é na verdade uma frase muito auto-descritiva. Se alguém está olhando o código e quer raciocinar o que faz, é fácil =)
Ok, quebrando tudo. Se você está olhando para o código, geralmente deseja que ele faça alguma coisa. Você quer ter certeza de que faz o que acha que deve fazer. Então, você desenvolve teorias sobre o que o código deveria estar fazendo e, em seguida, argumenta sobre isso para tentar argumentar por que o código realmente funciona. Você tenta pensar no código como um ser humano (e não como um computador) e tenta racionalizar argumentos sobre o que o código pode fazer.
O pior caso para "fácil de raciocinar" é quando a única maneira de entender o que o código faz é passar linha por linha através do código como uma máquina de Turing para todas as entradas. Neste caso, a única maneira de razão qualquer coisa sobre o código é para se transformar em um computador e executá-lo em sua cabeça. Esses piores exemplos de casos são facilmente vistos em concursos de programação obsfucados, como essas 3 linhas de PERL que descriptografam o RSA:
Quanto a fácil raciocinar, novamente, o termo é altamente cultural. Você deve considerar:
Cada um deles afeta "fácil de raciocinar" de maneira diferente. Tome as habilidades do raciocínio como exemplo. Quando comecei na minha empresa, foi recomendado que eu desenvolvesse meus scripts no MATLAB porque é "fácil de raciocinar". Por quê? Bem, todos na empresa conheciam o MATLAB. Se eu escolhesse um idioma diferente, seria mais difícil para alguém me entender. Não importa que a legibilidade do MATLAB seja atroz para algumas tarefas, simplesmente porque não foi projetada para elas. Mais tarde, à medida que minha carreira avançava, o Python se tornou cada vez mais popular. De repente, o código do MATLAB tornou-se "difícil de raciocinar" e o Python era a linguagem preferida para escrever código que era fácil de raciocinar.
Considere também quais idiomas o leitor pode ter. Se você pode confiar no seu leitor para reconhecer uma FFT em uma sintaxe específica, é "mais fácil raciocinar sobre" o código se você se ater a essa sintaxe. Ele permite que eles olhem o arquivo de texto como uma tela na qual você pintou uma FFT, em vez de ter que entrar nos mínimos detalhes. Se você estiver usando C ++, descubra o quanto seus leitores estão confortáveis com a
std
biblioteca. Quanto eles gostam de programação funcional? Alguns dos idiomas que saem das bibliotecas de contêineres são muito dependentes de qual estilo idomatic você prefere.Também é importante entender que tipos de perguntas o leitor pode estar interessado em responder. Seus leitores estão preocupados principalmente com a compreensão superficial do código ou estão procurando bugs nas entranhas?
O quão certo o leitor precisa ser é realmente interessante. Em muitos casos, o raciocínio nebuloso é realmente suficiente para levar o produto para fora da porta. Em outros casos, como o software de voo da FAA, o leitor desejará ter um raciocínio rígido. Encontrei um caso em que argumentei usar o RAII para uma tarefa específica, porque "Você pode simplesmente configurá-lo e esquecê-lo ... ele fará a coisa certa". Foi-me dito que eu estava errado sobre isso. Aqueles que pensariam nesse código não eram o tipo de pessoa que "apenas quer esquecer os detalhes". Para eles, o RAII era mais como um chad, forçando-os a pensar em todas as coisas que podem acontecer quando você sai do escopo.
fonte