Aqui está um problema de programação / linguagem em que gostaria de ouvir seus pensamentos.
Desenvolvemos convenções que a maioria dos programadores deve seguir, que não fazem parte da sintaxe das linguagens, mas servem para tornar o código mais legível. É claro que sempre é uma questão de debate, mas há pelo menos alguns conceitos fundamentais que a maioria dos programadores consideram agradáveis. Nomear suas variáveis adequadamente, nomeando em geral, tornando suas linhas não excessivamente longas, evitando funções longas, encapsulamentos, essas coisas.
No entanto, há um problema que ainda não encontrei alguém comentando e que talvez seja o maior do grupo. É o problema dos argumentos serem anônimos quando você chama uma função.
Funções derivam da matemática, onde f (x) tem um significado claro, porque uma função tem uma definição muito mais rigorosa do que normalmente faz na programação. Funções puras em matemática podem fazer muito menos do que na programação e são uma ferramenta muito mais elegante, geralmente usam apenas um argumento (que geralmente é um número) e sempre retornam um valor (também geralmente um número). Se uma função recebe vários argumentos, quase sempre são apenas dimensões extras do domínio da função. Em outras palavras, um argumento não é mais importante que os outros. Eles são explicitamente ordenados, com certeza, mas fora isso, eles não têm ordenação semântica.
Na programação, no entanto, temos mais funções de definição de liberdade e, neste caso, eu argumentaria que não é uma coisa boa. Uma situação comum, você tem uma função definida como esta
func DrawRectangleClipped (rectToDraw, fillColor, clippingRect) {}
Observando a definição, se a função estiver escrita corretamente, é perfeitamente claro o que é o quê. Ao chamar a função, você pode até ter alguma mágica de conclusão de código / inteligência acontecendo no seu IDE / editor que lhe dirá qual deve ser o próximo argumento. Mas espere. Se eu precisar disso quando realmente estiver escrevendo, não há algo faltando aqui? A pessoa que lê o código não tem o benefício de um IDE e, a menos que salte para a definição, não tem idéia de qual dos dois retângulos passados como argumentos é usado para quê.
O problema vai além disso. Se nossos argumentos vierem de alguma variável local, pode haver situações em que nem sabemos qual é o segundo argumento, pois apenas vemos o nome da variável. Tomemos, por exemplo, esta linha de código
DrawRectangleClipped(deserializedArray[0], deserializedArray[1], deserializedArray[2])
Isso é aliviado em várias extensões em idiomas diferentes, mas mesmo em idiomas estritamente digitados e mesmo se você nomear suas variáveis de maneira sensata, nem mesmo menciona o tipo de variável ao passar para a função.
Como geralmente acontece com a programação, existem muitas soluções em potencial para esse problema. Muitos já estão implementados em idiomas populares. Parâmetros nomeados em C #, por exemplo. No entanto, tudo o que sei tem desvantagens significativas. Nomear cada parâmetro em cada chamada de função não pode levar a um código legível. Quase parece que estamos superando as possibilidades que a programação em texto simples nos oferece. Passamos do APENAS texto em quase todas as áreas, mas ainda codificamos o mesmo. Mais informações são necessárias para serem exibidas no código? Adicione mais texto. De qualquer forma, isso está ficando um pouco tangencial, então vou parar por aqui.
Uma resposta que cheguei ao segundo trecho de código é que você provavelmente descompactaria o array para algumas variáveis nomeadas e as usaria, mas o nome da variável pode significar muitas coisas e a maneira como é chamada não indica necessariamente o caminho que deve ser interpretado no contexto da função chamada. No escopo local, você pode ter dois retângulos nomeados leftRectangle e rightRectangle porque é isso que eles representam semanticamente, mas não precisa se estender ao que eles representam quando atribuídos a uma função.
De fato, se suas variáveis são nomeadas no contexto da função chamada, você está introduzindo menos informações do que poderia potencialmente com essa chamada de função e, em algum nível, se isso levar a um código com código pior. Se você possui um procedimento que resulta em um retângulo armazenado em rectForClipping e outro procedimento que fornece retForDrawing, a chamada real para DrawRectangleClipped é apenas uma cerimônia. Uma linha que não significa nada de novo e existe apenas para que o computador saiba exatamente o que você deseja, mesmo que você já o tenha explicado com a sua nomeação. Isso não é uma coisa boa.
Eu realmente adoraria ouvir novas perspectivas sobre isso. Tenho certeza de que não sou o primeiro a considerar isso um problema, então como é resolvido?
Respostas:
Concordo que o modo como as funções são frequentemente usadas pode ser uma parte confusa da escrita do código e, principalmente, da leitura do código.
A resposta para esse problema depende parcialmente do idioma. Como você mencionou, o C # nomeou parâmetros. A solução da Objective-C para esse problema envolve nomes de métodos mais descritivos. Por exemplo,
stringByReplacingOccurrencesOfString:withString:
é um método com parâmetros claros.No Groovy, algumas funções usam mapas, permitindo uma sintaxe como a seguinte:
Em geral, você pode resolver esse problema limitando o número de parâmetros que você passa para uma função. Eu acho que 2-3 é um bom limite. Se parece que uma função precisa de mais parâmetros, isso me leva a repensar o design. Mas, isso pode ser mais difícil de responder em geral. Às vezes você está tentando fazer muito em uma função. Às vezes, faz sentido considerar uma classe para armazenar seus parâmetros. Além disso, na prática, muitas vezes acho que funções que usam um grande número de parâmetros normalmente têm muitos deles como opcionais.
Mesmo em uma linguagem como Objective-C, faz sentido limitar o número de parâmetros. Uma razão é que muitos parâmetros são opcionais. Por exemplo, consulte rangeOfString: e suas variações no NSString .
Um padrão que costumo usar em Java é usar uma classe de estilo fluente como parâmetro. Por exemplo:
Isso usa uma classe como parâmetro e, com uma classe de estilo fluente, cria um código facilmente legível.
O snippet Java acima também ajuda onde a ordem dos parâmetros pode não ser tão óbvia. Normalmente assumimos com coordenadas que X vem antes de Y. E normalmente vejo a altura antes da largura como uma convenção, mas isso ainda não é muito claro (
something.draw(5, 20)
).Eu também vi algumas funções como,
drawWithHeightAndWidth(5, 20)
mas mesmo essas não podem ter muitos parâmetros, ou você começa a perder a legibilidade.fonte
Dimension(int width, int height)
eGridLayout(int rows, int cols)
(o número de linhas é a altura, o significadoGridLayout
tem a altura primeiro eDimension
a largura).array_filter($input, $callback)
contraarray_map($callback, $input)
,strpos($haystack, $needle)
contraarray_search($needle, $haystack)
É resolvido principalmente pela boa nomeação de funções, parâmetros e argumentos. Você já explorou isso e descobriu que tinha deficiências, no entanto. A maioria dessas deficiências é atenuada, mantendo as funções pequenas, com um pequeno número de parâmetros, tanto no contexto de chamada quanto no contexto de chamada. Seu exemplo específico é problemático porque a função que você está chamando está tentando fazer várias coisas ao mesmo tempo: especifique um retângulo base, especifique uma região de recorte, desenhe-a e preencha-a com uma cor específica.
É como tentar escrever uma frase usando apenas os adjetivos. Coloque mais verbos (chamadas de função) lá, crie um assunto (objeto) para sua frase e é mais fácil ler:
Mesmo se
clipRect
ecolor
tiver nomes terríveis (e eles não deveriam), você ainda pode discernir seus tipos do contexto.Seu exemplo desserializado é problemático porque o contexto de chamada está tentando fazer muita coisa de uma só vez: desserializando e desenhando alguma coisa. Você precisa atribuir nomes que façam sentido e separem claramente as duas responsabilidades. No mínimo, algo como isto:
Muitos problemas de legibilidade são causados por tentar ser muito conciso, ignorando os estágios intermediários que os humanos precisam para discernir o contexto e a semântica.
fonte
Na prática, é resolvido com um design melhor. É excepcionalmente incomum que funções bem escritas obtenham mais de 2 entradas e, quando isso ocorre, é incomum que muitas dessas entradas não possam ser agregadas a algum pacote coeso. Isso facilita bastante a quebra de funções ou a agregação de parâmetros, para que você não faça com que uma função faça muito. Um tem duas entradas, fica fácil nomear e muito mais claro sobre qual entrada é qual.
Minha linguagem de brinquedo tinha o conceito de frases para lidar com isso, e outras linguagens de programação mais focadas em linguagem natural tiveram outras abordagens para lidar com isso, mas todas elas tendem a ter outras desvantagens. Além disso, mesmo as frases são pouco mais que uma boa sintaxe para tornar as funções com nomes melhores. Sempre será difícil criar um bom nome de função quando são necessárias várias entradas.
fonte
Em Javascript (ou ECMAScript ), por exemplo, muitos programadores se acostumaram a
E, como prática de programação, passou de programadores para suas bibliotecas e de lá para outros programadores que passaram a gostar, usar e escrever mais bibliotecas, etc.
Exemplo
Em vez de ligar
como isso:
, que é um estilo válido e correto, você chama o
como isso:
, que é válido, correto e agradável em relação à sua pergunta.
Obviamente, é necessário que haja condições adequadas para isso - em Javascript isso é muito mais viável do que em C. digamos C. Em javascript, isso deu origem a notação estrutural amplamente usada que se tornou popular como uma contraparte mais leve do XML. Chama-se JSON (você já deve ter ouvido falar sobre isso).
fonte
params
(geralmente contendo argumentos opcionais e geralmente opcionais) como, por exemplo, esta função . Isso torna as funções com muitos argumentos bastante fáceis de entender (no meu exemplo, existem 2 argumentos obrigatórios e 6 opções).Você deve usar o objetivo-C, então, aqui está uma definição de função:
E aqui é usado:
Acho que o ruby tem construções semelhantes e você pode simulá-las em outros idiomas com listas de valores-chave.
Para funções complexas em Java, eu gosto de definir variáveis fictícias na redação das funções. Para o seu exemplo esquerdo-direito:
Parece mais codificação, mas você pode, por exemplo, usar leftRectangle e refatorar o código posteriormente com "Extrair variável local" se achar que não será compreensível para um mantenedor futuro do código, que pode ou não ser você.
fonte
Minha abordagem é criar variáveis locais temporárias - mas não apenas chamá-las
LeftRectange
eRightRectangle
. Em vez disso, uso nomes um pouco mais longos para transmitir mais significado. Costumo tentar diferenciar os nomes o máximo possível, por exemplo, não chamar os doissomething_rectangle
, se o papel deles não for muito simétrico.Exemplo (C ++):
e posso até escrever uma função ou modelo de wrapper de uma linha:
(ignore oe comercial, se você não conhece C ++).
Se a função fizer várias coisas estranhas sem uma boa descrição - provavelmente precisará ser refatorada!
fonte