Mentira 2: O código deve ser projetado em torno de um modelo do mundo? [fechadas]

23

Recentemente, li a postagem no blog Three Big Lies e estou tendo dificuldades para justificar a segunda mentira, citada aqui:

(MENTIRA # 2) O CÓDIGO DEVE SER PROJETADO EM TORNO DE UM MODELO DO MUNDO

Não há valor no código ser algum tipo de modelo ou mapa de um mundo imaginário. Não sei por que este é tão atraente para alguns programadores, mas é extremamente popular. Se houver um foguete no jogo, tenha certeza de que existe uma classe "Foguete" (supondo que o código seja C ++) que contém dados para exatamente um foguete e faz coisas perigosas. Sem levar em consideração o que realmente está sendo feito na transformação de dados ou o layout dos dados. Ou, nesse caso, sem o entendimento básico de que onde há uma coisa, provavelmente há mais de uma.

Embora existam muitas penalidades de desempenho para esse tipo de design, a mais significativa é que ela não é dimensionada. Em absoluto. Cem foguetes custam cem vezes mais que um foguete. E é extremamente provável que custe ainda mais do que isso! Mesmo para quem não é programador, isso não deve fazer sentido. Economia de escala. Se você tem mais de algo, deve ficar mais barato, não mais caro. E a maneira de fazer isso é projetar os dados corretamente e agrupar as coisas por transformações semelhantes.

Aqui estão meus problemas com essa mentira em particular.

  1. Existe valor no código ser um modelo / mapa de um mundo imaginário, pois a modelagem do mundo imaginário ajuda (pelo menos eu, pessoalmente) a visualizar e organizar o código.

  2. Ter uma aula "Rocket" é, para mim, uma escolha perfeitamente válida para uma aula. Talvez "foguetes" possam ser divididos em tipos de foguetes como AGM-114 Hellfire, etc., que conteriam força de carga, velocidade máxima, raio máximo de rotação, tipo de mira e assim por diante, mas ainda assim todos os foguetes disparados precisariam ter uma posição e uma velocidade.

  3. É claro que ter 100 Rockets custa mais de 1 Rocket. Se houver 100 Rockets na tela, deve haver 100 cálculos diferentes para atualizar sua posição. O segundo parágrafo parece afirmar que, se houver 100 Rockets, custará menos de 100 cálculos para atualizar o estado?

Meu problema aqui é que o autor apresenta um modelo de programação "defeituoso", mas não apresenta uma maneira de "corrigi-lo". Talvez eu esteja explorando a analogia da classe Rocket, mas eu realmente gostaria de entender o raciocínio por trás dessa mentira. Qual é a alternativa?

thndrwrks
fonte
9
@gnat: Esta questão está diretamente dentro da província de design de software, então estou inclinado a dar uma margem de manobra.
Robert Harvey
12
Essa postagem no blog é muito mal escrita e não defende nem apóia suas reivindicações muito bem. Eu não pensaria muito nisso.
Whatsisname
21
Quem escreveu essa citação é um idiota com pouco entendimento dos conceitos de OO ou como eles são implementados em software. Primeiro, não estamos mapeando para um mundo imaginário, estamos mapeando para o mundo real. E se você tiver 100 foguetes, apenas o estado de foguetes adicionais usa recursos adicionais, não o modelo ou o comportamento. Ele parece ter idéias diferentes sobre o assunto e sugere corrigir um problema que não existe. "Agrupar coisas semelhantes" como uma otimização pode fazer sentido às vezes, mas é totalmente independente do uso de classes ou não. Se você quiser aprender, evite esse charlatão.
Martin Maat
3
Considerando que o autor não se deu ao trabalho de escrever mais de cinco parágrafos no total explicando as "3 grandes mentiras", você provavelmente passou mais tempo pensando no artigo do que ele. Se ele não se incomodar em fazer um esforço, você também não deveria.
Caleb
9
Eu acho que o que ele está dizendo é: você realmente precisa de 100 "objetos de foguete" (provavelmente alocados dinamicamente também com métodos virtuais), em oposição a uma lista de posições, uma lista de velocidades etc. (com uma lista de todas as posições e um lista de velocidades significa que você pode ser capaz de usar instruções vetor para adicionar a velocidade para a posição em cada atualização carrapato em vez de escrever um loop ingênuo através de uma lista de objetos)
Random832

Respostas:

63

Primeiramente, vejamos algum contexto: este é um designer de jogos escrevendo em um blog cujo assunto está obtendo a última gota de desempenho de uma CPU Cell BE. Em outras palavras: trata-se de programação de jogos de console, mais especificamente, programação de jogos de console para o PlayStation 3.

Agora, os programadores de jogos são um grupo curioso, os programadores de jogos de console ainda mais, e o Cell BE é uma CPU bastante estranha. (Há uma razão pela qual a Sony adotou um design mais convencional para o PlayStation 4!)

Então, temos que olhar para essas afirmações dentro deste contexto.

Existem também algumas simplificações nessa postagem do blog. Em particular, essa mentira nº 2 é mal apresentada.

Eu argumentaria que tudo o que abstrai do mundo real é um modelo em algum sentido. E como o software não é real, mas virtual, é sempre uma abstração e, portanto, sempre um modelo. Mas! Um modelo não precisa ter um mapeamento 1: 1 limpo para o mundo real. Afinal, isso é o que o torna um modelo em primeiro lugar.

Então, em certo sentido, o autor está claramente errado: software é um modelo. Período. Em algum outro sentido, ele está certo: esse modelo não precisa se parecer com o mundo real.

Vou dar um exemplo que já dei em algumas outras respostas ao longo dos anos, o (in) famoso exemplo de introdução à conta bancária OO 101. Veja como é uma conta bancária em quase todas as classes OO:

class Account {
  var balance: Decimal
  def transfer(amount: Decimal, target: Account) = {
    balance -= amount
    target.balance += amount
  }
}

Então: o balanceé de dados , e transferé uma operação .

Mas! Veja como é uma conta bancária em quase todos os softwares bancários de todos os tempos:

class TransactionSlip {
  val transfer(amount: Decimal, from: Account, to: Account)
}

class Account {
  def balance = 
    TransactionLog.filter(t => t.to == this).map(_.amount).sum - 
    TransactionLog.filter(t => t.from == this).map(_.amount).sum
}

Então, agora transfersão dados e balancesão uma operação (uma dobra à esquerda sobre o log de transações). (Você também notará que TransactionSlipé imutável, balanceé uma função pura, TransactionLogpode ser uma estrutura de dados "quase" imutável somente para acréscimo ... Tenho certeza de que muitos de vocês viram os erros de simultaneidade flagrantes na primeira implementação, que agora desaparecem magicamente .)

Observe que esses dois são modelos. Ambos são igualmente válidos. Ambos estão corretos. Ambos modelam a mesma coisa. E, no entanto, eles são exatamente duplos entre si: tudo o que são dados em um modelo é uma operação no outro modelo, e tudo o que é uma operação em um modelo é dados no outro modelo.

Portanto, a questão não é se você modela o "mundo real" em seu código, mas como você o modela.

Como se vê, o segundo modelo é, na verdade, também como o sistema bancário funciona no mundo real. Como eu sugerido acima, este segundo modelo é principalmente imutável e puro, e imune a erros de simultaneidade, que é realmente muito importante se considerarmos que houve um tempo não muito tempo atrás, em que TransactionSlips eram reais pedaços de papel que foram enviadas ao redor via cavalo e carruagem.

No entanto, o fato de esse segundo modelo realmente corresponder ao funcionamento do sistema bancário real e ao funcionamento do software bancário do mundo real não o torna automaticamente mais "certo". Porque, na verdade, o primeiro modelo ("errado") se aproxima bastante da maneira como os clientes bancários veem seu banco. Para eles , transferé uma operação (eles precisam preencher um formulário) e balanceé uma parte dos dados na parte inferior do extrato da conta.

Assim, ele pode muito bem ser verdade que no código do motor núcleo jogo de um jogo de tiro PS3 de alto desempenho, não haverá um Rockettipo, mas ainda assim, haverá alguma modelagem do mundo acontecendo, mesmo se o modelo parece estranho para alguém que não é especialista no domínio da programação de mecanismos de física de jogos de console.

Jörg W Mittag
fonte
1
Isso não significa que um bom código modela o mundo real e que, na verdade, é apenas um mal-entendido do mundo real que causa um modelo ruim e, portanto, um código ruim?
yitzih 7/09/16
14
Prefiro dizer que "às vezes o mundo real não é o que você pensa que é" ou "o que é 'mundo real' depende do contexto". (Novamente, para um proprietário de conta bancária, os dados na parte inferior do extrato são muito reais, enquanto para um funcionário do banco são efêmeros.) Acho que o extrato na postagem do blog é causado principalmente pelo autor que não entende que "modelagem o mundo real "não significa" tirar uma foto e transformar tudo o que você vê ali em uma classe ".
Jörg W Mittag
O front-end do seu aplicativo de banco on-line provavelmente tratará balancecomo dados e transações como mais dados e será transferido como operações, porque é isso que o usuário vê, mesmo que o back-end possa tratá-lo de maneira diferente.
user253751
@ yitzih: todo modelo é uma abstração e simplificação, então você pode acusar todos os modelos de estarem incorretos, mas isso não é construtivo. Todo modelo precisa cumprir um propósito e deve ser bom o suficiente para isso, não desperdiçando recursos para coisas desnecessárias. Para o software de um governo, um humano pode ser alguém que pode participar de eleições, tem que pagar impostos ou pode se casar com outro humano. Para o nosso software de CRM, um humano é alguém associado a pedidos e com endereço de entrega (e nenhum modelos how (s) que ele come) ...
Holger
2
Se o ser humano souber alguma coisa sobre bancos , achará o segundo mais fácil e, como as técnicas bancárias que eles conhecem foram inventadas para fazer o trabalho bancário, eles podem criar um software bancário que funcione. Não porque o segundo modelo é "mais parecido com o mundo real", mas porque descreve um banco melhor. O primeiro modelo pode ser uma representação igualmente precisa de um banco disfuncional do mundo real! Adivinha o quê: se você deseja um bom software bancário, os programadores precisam aprender a fazer bem os serviços bancários, mesmo que apenas a partir dos documentos de requisitos.
9788 Steve Jobs (
19

Eu discordo de toda "mentira" que ele propõe.

TL; DR O autor deste artigo estava tentando ser controverso para tornar seu artigo mais interessante, mas as chamadas "mentiras" são aceitas pelos desenvolvedores de software por boas razões.

Mentira # 1 - Big O é importante para fins de dimensionamento. Ninguém se importa se um aplicativo minúsculo demora mais tempo e é a única constante de tempo que importa, eles se importam que, quando dobram o tamanho da entrada, não multiplicam o tempo de execução por um fator de 10.

Mentira # 2 - A modelagem de programas após o mundo real permite que um programador que analisa seu código três anos depois entenda facilmente o que está fazendo. O código precisa ser de manutenção ou você precisará passar horas apenas tentando entender o que o programa está tentando fazer. Outra resposta sugeriu que você pode ter mais classes genéricas como LaunchPade MassiveDeviceMover. Não é necessariamente uma classe ruim, mas você ainda precisaria da Rocketclasse. Como alguém deve saber o que MassiveDeviceMoverfaz ou o que se move? Está movendo montanhas, naves espaciais ou planetas? Isso basicamente significa que adicionar classes como MassiveDeviceMovertorna seu programa menos eficiente (mas possivelmente muito mais legível e compreensível).

Além disso, o custo do tempo do desenvolvedor começou a exceder o custo do hardware há muito tempo. É uma idéia horrível começar tentando projetar com otimização na frente de seus pensamentos. Você programá-lo da maneira fácil e compreensível e depois ajustá-lo depois de descobrir quais partes de seus programas estão demorando muito tempo para serem executadas. Não se esqueça: 80% do tempo de execução está sendo usado por 20% do programa.

Mentira # 3 - O código é extremamente importante. Um código bem escrito (e modular) permite a reutilização (economizando inúmeras horas de trabalho). Ele também permite que você examine e reconheça dados ruins para que possam ser manipulados. Os dados são maravilhosos, mas sem o código, seria impossível analisar e obter informações úteis.

yitzih
fonte
5
Eu acho que sou mais simpático ao # 3. Em 30 anos de programação, a grande maioria dos bugs, problemas de desempenho e outros problemas que eu vi foram resolvidos corrigindo a representação dos dados. Se os dados estiverem corretos, o código praticamente se escreve.
Lee Daniel Crocker
6
O verdadeiro problema do item 3 é que ele compara maçãs com laranjas, não que o código seja mais importante que os dados ou vice-versa.
Doc Brown
3
Os dados de entrada estão fora de suas mãos, mas como representar os dados em seu software está inteiramente dentro deles. Você pode estar chamando essa parte de "codificação", mas acho que não é: por exemplo, geralmente é independente da linguagem e geralmente é feita antes do início de qualquer codificação. Concordo, porém, que o código que limpa os feios dados de entrada geralmente é uma coisa boa; mas você não pode fazer isso até ter uma definição de "limpo".
Lee Daniel Crocker
3
Eu não acho que a mentira nº 3 seja uma mentira, na verdade. Fred Brooks já escreveu décadas atrás: "Mostre-me seus fluxogramas e oculte suas tabelas, e continuarei confuso. Mostre-me suas tabelas e geralmente não precisarei de seus fluxogramas; eles serão óbvios". (Atualmente, provavelmente falaríamos sobre "algoritmos" e "tipos de dados" ou "esquemas".) Portanto, a importância dos dados é bem conhecida há muito tempo.
Jörg W Mittag
1
@djechlin Meu argumento não era que os dados não são importantes ou que o código é mais importante. Simplesmente esses dados não são mais importantes que o código. Ambos são muito importantes e dependem um do outro fortemente.
yitzih 8/09/16
6

Em um sistema de comércio eletrônico, você não lida com "foguetes" em nível de classe, mas com "produtos". Portanto, depende do que você está tentando realizar e do seu nível de abstração desejado.

Em um jogo, pode-se argumentar que os foguetes são apenas um dos muitos tipos de "objetos em movimento". A mesma física se aplica a eles como a todos os outros objetos em movimento. Portanto, no mínimo, o "foguete" herdará de uma classe base mais geral de "objeto em movimento".

De qualquer forma, o autor da passagem que você citou parece ter exagerado um pouco o caso. Os foguetes ainda podem ter características únicas, como "quantidade de combustível restante" e "empuxo", e você não precisa de cem classes para representá-lo para cem foguetes, precisa apenas de um. A criação de objetos é um custo bastante baixo na maioria das linguagens de programação decentes; portanto, se você precisar rastrear coisas semelhantes a foguetes, a noção de que você não deve criar objetos Rocket porque pode ser muito caro não faz muito sentido.

Robert Harvey
fonte
5

O problema com o mundo real é toda essa maldita física. Separamos as coisas em objetos físicos no mundo real porque são mais fáceis de mover do que átomos individuais, ou uma escória gigante derretida de algo que pode ser um foguete.

Da mesma forma, o mundo real fornece vários recursos úteis nos quais confiamos. É realmente fácil fazer exceções do Penguin - "todos os pássaros voam, exceto ...". E é realmente fácil rotular as coisas como foguetes, quero dizer, se eu chamar esse pinguim de foguete e acendê-lo ... simplesmente não funciona.

Então, como separamos as coisas no mundo real funciona conceitualmente sob essas restrições. Quando fazemos coisas no código, devemos separar as coisas para que funcionem bem sob essas restrições, que são decididamente diferentes.

Qual é a alternativa?

Pense em redes. Não modelamos portas, fios e roteadores no código. Em vez disso, abstraímos a comunicação de rede em conexões e protocolos. Fazemos isso porque é uma abstração útil, independentemente da implementação no mundo real. E coloca restrições úteis (por exemplo: você não pode se comunicar até que a conexão seja aberta) que só importam no código .

Então, sim, às vezes modelar código depois que o mundo real funciona, mas isso é uma coincidência . Quando as pessoas falam sobre POO, os objetos não são objetos do mundo real. Que escolas e tutoriais digam o contrário é uma tragédia de décadas.

Telastyn
fonte
1
+1: Os protocolos são muito uma abstração "mundo real". Mesmo no mundo de hoje, os oficiais do protocolo são algumas das pessoas mais importantes da equipe para uma visita de estado, por exemplo. Quem aparece primeiro no tapete vermelho na reunião do G8, Obama ou Putin? Eles abraçam ou apertam as mãos? Como saúdo um árabe x um indiano? E assim por diante. Temos muitas "coisas" no "mundo real" que não são "coisas" no "mundo físico". Modelar o mundo real não significa modelar o mundo físico. Mesmo se não houver um Rockettipo de código desse cara, eu estou disposto a apostar que há, contudo, algum modelo de ...
Jörg W Mittag
... o mundo real, mesmo que não corresponda a nada "físico" (no sentido de "tocável"). Eu não ficaria surpreso ao encontrar objetos "físicos" reais (no sentido de "coisas que um físico poderia reconhecer") lá dentro, como quaterniões, tensores, campos etc., que são, é claro, também " coisas do mundo real "e" modelos do mundo real ".
Jörg W Mittag
Alan Kay imaginou o Dynabook como um computador que seria entregue às crianças ao nascer e que se tornaria uma extensão do cérebro. O objetivo do padrão MVC, então, seria fazer com que o View and Controller preenche a lacuna entre o cérebro e o Modelo para apoiar a Metáfora de Manipulação Direta, ou seja, a ilusão de que o computador é apenas uma extensão do cérebro e que se pode manipular diretamente os Objetos Modelo com os próprios pensamentos. E é isso que queremos dizer quando dizemos que o Modelo de Domínio modela o "mundo real". Deve implementar as abstrações em nossos cérebros.
Jörg W Mittag
E quando penso em um mecanismo de física de jogos de console, provavelmente não penso em foguetes e, portanto, não deveria haver um modelo de foguete no meu código. Mas provavelmente estou pensando em outros "pensamentos do mundo real" e deve haver modelos daqueles no código.
Jörg W Mittag
2

A alternativa é modelar as coisas com as quais seus programas se importam. Mesmo que seu programa lide com foguetes, talvez você não precise de uma entidade chamada a Rocket. Por exemplo, você pode ter uma LaunchPadentidade e uma LaunchScheduleentidade e uma MassiveDeviceMoverentidade. O fato de tudo isso ajudar o lançamento de foguetes não significa que você esteja lidando com eles.

BobDalgleish
fonte
0

Meu problema aqui é que o autor apresenta um modelo de programação "defeituoso", mas não apresenta uma maneira de "corrigi-lo". Talvez eu esteja explorando a analogia da classe Rocket, mas eu realmente gostaria de entender o raciocínio por trás dessa mentira. Qual é a alternativa?

Esse é o verdadeiro problema, mas eu darei a você como desenvolvedor, talvez isso ajude.

Primeiro, eu não chamaria isso de mentira, como equívocos comuns. Chamar isso de mentira é apenas um hype.

Um Ele está certo, de certa forma. Não vou gastar muito tempo nisso, porque isso não faz parte da questão. Mas, em essência, ele está correto. Eu poderia reafirmar isso como "O que funciona em um laboratório pode não funcionar na vida real". Muitas vezes os desenvolvedores aderem a um design que funciona em um "laboratório", mas falha em aplicativos do mundo real.

Três Soa um pouco quadradão para mim, mas essencialmente ele está correto novamente. Mas isso pode ser reescrito para "escrever código de acordo com suas necessidades, não tente ajustá-las ao seu código".

Dois Novamente, aqui ele está correto. Vi desenvolvedores passarem semanas ou mais desenvolvendo uma classe "foguete" quando uma simples classe "veículo" funcionaria, ou uma classe ainda mais simples, "móvel". Se tudo o que seu foguete precisa fazer é mover-se do lado esquerdo da tela para a direita e emitir um som, você poderá usar a mesma classe que usou para carros, trens, barcos e moscas. Os 100 devem custar menos que 1 * 100 argumento parece estar no tempo gasto desenvolvendo, e não tanto em custos de computação. Embora aderir a menos classes gerais que sejam reutilizadas seja "mais barato", muitas classes específicas que não podem ser reutilizadas. Provavelmente, isso pode ser reescrito porque "as classes gerais são melhores do que as classes específicas,

Em essência, o artigo inteiro poderia ser reescrito, com menos chavões e teria apenas um parágrafo na melhor das hipóteses. Dito isto, é um post de blog focado em uma área estreita de programação. Fiz alguma programação incorporada e posso concordar com a idéia geral por trás dessas declarações, embora exista um pouco de "fluff" ao redor delas para torná-la adequada para uma apresentação na GDC.

Uma última nota, o artigo foi escrito em 2008 (o melhor que eu poderia dizer). As coisas mudam rapidamente. As afirmações são verdadeiras hoje, mas os sistemas embarcados são muito mais comuns hoje e naquela época, e os padrões de desenvolvimento mudam. Talvez até em resposta a este artigo / conversa.

coteyr
fonte
-1

Acho interessante que estas se concentrem em preocupações acadêmicas: a plataforma, a eficiência do uso da memória e os dados. Mas ignora completamente o elemento humano.

Software é atender às necessidades das pessoas. Normalmente, isso é quantificado em termos de negócios - existem clientes que desejam algo e apoiadores que estão dispostos a pagar para que isso aconteça. Se o software está sendo escrito de forma a atender às necessidades de ambos os lados da equação, então é um software bom; se não, é um software ruim.

Se a plataforma não é importante para o cliente, a plataforma não é importante. Se a eficiência da memória não é importante para o cliente, não é importante. Se os dados não são importantes para o cliente, eles não são importantes. Se o código funcionar, mas não puder ser lido ou mantido, e o cliente desejar alterações rápidas e confiáveis ​​por um preço razoável, um código mal escrito é uma coisa ruim. Se o código funciona, mas não pode ser lido ou mantido, e o cliente não se importa ou está disposto a pagar por refatores caros, um código mal escrito é uma coisa boa.

A grande mentira é que qualquer coisa, menos o elemento humano, é importante. Por que os dados são importantes? Porque há algum cliente ou parte interessada que precisa ser. Essa é a "grande verdade".

Preço Jones
fonte
4
Infelizmente, os clientes desejam código rápido e sujo, fácil de ler e manter, barato, sem esforços de teste e sem bugs.
217166 Gonçalo I
@ user889742 Ha! Verdade. Você declarou precisamente os arquitetos problema de engenharia vêm tentando resolver para todos os tempos e que faz a indústria um espaço tão interessante para trabalhar.
Preço Jones
Ele ignora o elemento humano porque é desenvolvedor de jogos e a era de manutenção de um jogo é relativamente curta, embora hoje mais do que em 2008. Os patches do primeiro dia parecem ser a norma nos jogos atualmente. Em 2008, os patches para jogos ainda eram relativamente raros.
precisa saber é o seguinte
-1

IMHO Se o código é "projetado em torno de um modelo do mundo", é mais fácil entender, tanto para designers quanto para desenvolvedores e para os mantenedores. Mas acho que não sou só eu, e não apenas software. Wikipedia: Modelagem científica é uma atividade científica, cujo objetivo é tornar uma parte ou característica específica do mundo mais fácil de entender, definir, quantificar, visualizar ou simular, referenciando-a ao conhecimento existente e geralmente aceito

Vou eu
fonte