Uma das maneiras mais rápidas de se tornar um especialista e gerar novas ideias é abordar um tópico de um ângulo não relacionado.

Quer aprender a programar, mas nunca estudou? Abordagem de programação munida de outro vocabulário. Seja carpintaria, biologia ou matemática, cada um oferece ferramentas e modelos para a compreensão. Não só para entender, mas para contribuir. As melhores ideias são o resultado da combinação de pensamentos e técnicas não relacionadas.

Então, eu quero saber a resposta para a seguinte pergunta:

Qual é o objeto do design de software?

Abordarei essa questão relacionando ideias em filosofia, especificamente metafísica e filosofia da linguagem, com software. Começarei introduzindo distinções da filosofia e, em seguida, analisarei suas contrapartes em design de software. Em seguida, responda à pergunta.

Três níveis de linguagem, três níveis de código

Suponha que você queira criar um aplicativo de meditação, como o Stoa. Você começará com especificações de recursos, como permitir que os usuários ouçam e acompanhem meditações. Quando houver clareza suficiente aqui, passe para o design do modelo de dados. Comece perguntando, quais são as peças fundamentais no sistema? Meditações, usuários, playlists, cursos? Quais são as propriedades de cada um desses componentes? Nesta etapa, você está fazendo o design do código. E você está tomando decisões importantes, decisões que podem afetar você, outros desenvolvedores e usuários por anos.

Mas qual é o objeto do design? Outra maneira de perguntar isso é: quando você está projetando software, o que você está projetando? A resposta óbvia é “código” ou “software” – mas isso é impreciso. Uma resposta mais precisa oferece uma maneira melhor de pensar sobre design de código, ajudando a tomar melhores decisões.

Para mostrar o porquê, vamos começar com três ideias relacionadas, mas distintas, da filosofia da linguagem: enunciados, sentenças e proposições.

O que isso significa?

Um enunciado refere-se ao dizer de uma frase. Se eu disser: “Parece que Dion está programando em Ruby”, isso seria uma frase em inglês. Se eu mandar uma mensagem para meu amigo, “Dion está programando em Ruby”, isso também contaria como um enunciado.

As frases são itens linguísticos familiares. Esta é uma sentença! Próximo.

Pense em uma proposição como o que é expresso por uma frase. Quando eu digo que Dion está programando em Ruby, então há um estado de coisas representado, ou seja, o estado de coisas onde Dion está programando Ruby. A proposição é verdadeira ou falsa. Dion está programando Ruby ou não. Também posso expressar a mesma proposição com uma frase distinta em uma linguagem diferente: “Dion está programando en Ruby”. Proposições não são sentenças.

Assim, a fala tem três níveis: o enunciado, a sentença e a proposição. Você pode pensar no código como tendo três níveis que mapeiam os três níveis da fala: execução, representação concreta e representação abstrata.

No primeiro nível, o nível de execução, o código é executado com parâmetros específicos e valores de tempo de execução. Suponha que uma solicitação acione o ponto de extremidade de Dion: . Os dados e request determinam quais operações são executadas. À medida que o código é executado, ele carregará os valores (e provavelmente atribuirá novos) através de sua execução.PATCHmeditation/:meditation_id/edit:meditation_id

O nível de execução do código corresponde à emissão de uma sentença. A execução é uma instanciação de código (uma representação concreta); Um enunciado é uma instanciação de uma sentença.

No segundo nível, você tem a representação concreta. Aqui, pense no código escrito. Por exemplo, se você investigar o aplicativo Web do Dion, descobrirá que ele tem classes, métodos e assim por diante específicos. Essa representação ignora valores de tempo de execução e parâmetros específicos. Por exemplo, o ponto de extremidade de atualização para meditações não pressupõe que seja um número específico. Você pode atualizar um com qualquer ID válido.:meditation_idMeditation

Esse nível é concreto porque inclui detalhes de implementação, como a linguagem e a sintaxe de um programa. Ele pode ser instanciado em diferentes máquinas, em diferentes momentos, com diferentes parâmetros e valores de tempo de execução. Da mesma forma, uma frase pode ser proferida de diferentes maneiras, em diferentes momentos, em diferentes contextos. Assim, a representação concreta corresponde a uma sentença.

Aqui está um exemplo simples:

class Api::MeditationController
  def update
    meditation = Meditation.find(meditation_id)
        
    if meditation.update(meditation_attributes)
      render json: meditation.as_json
    else
      render json_error
    end
  end
end

No nível de execução, esse código terá valores específicos. Por exemplo, será atribuído um objeto específico (se houver) e terá um valor de retorno. No nível da representação concreta, estamos considerando o código como você o vê — abstraindo os valores específicos que ele pode ter durante a execução.@meditationMeditation@meditation.update

No terceiro nível, há o que chamarei de representação abstrata. O código de Dion representa objetos abstratos como um arquivo . Como um é abstrato, eles podem ser representados por código em outra linguagem. Neste exemplo, você pode pensar em um como um cluster de propriedades representado por uma definição de tipo:MeditationMeditationMeditation

interface Meditation {
  createdAt: DateTime,
  updatedAt: DateTime,
  title: String,
  description: String,
  ...
}

Observe que isso está descrevendo um objeto independente de linguagem. Uma representação abstrata pode ter muitas representações concretas. Além disso, uma representação concreta pode ter muitas representações abstratas. Por exemplo, considere:

const isEligible = person => {
  person?.account?.credit > N
}

Essa função pode ser verificar se uma pessoa tem crédito financeiro suficiente em sua conta bancária ou verificar se uma pessoa é elegível para uma recompensa com base em um sistema de crédito social em um videogame. Não dá para saber. Isso não é surpreendente, uma vez que as sentenças também podem representar diferentes proposições.

Uma representação abstrata pode ter muitas representações concretas diferentes. A escolha de linguagem e sintaxe é um detalhe da implementação. Da mesma forma, sentenças diferentes em línguas diferentes podem expressar as mesmas proposições.

Assim, temos as relações entre as três ideias:

  • Enunciado => Execução
  • Sentença => Representação Concreta
  • Proposição => Representação Abstrata

###
O objeto do design

Voltando à consulta original, qual é o objeto do design de software? Alguns programadores passam a maior parte do tempo preocupados com a representação concreta. Eles farão perguntas como: meu código está bem formado? Ele produz os resultados certos quando executado? Ele continuará a entregar os resultados certos quando executado com parâmetros diferentes, em um contexto diferente?

Isso é diferente da maioria dos trabalhos filosóficos. O objeto da filosofia é o mundo, representado pelas proposições. Um filósofo quer saber se os invertebrados são conscientes, qual é a natureza da explicação ou se é permitido deixar que os outros façam errado. Eles não estão preocupados com a análise de sentenças. Eles estão preocupados com as proposições que as sentenças expressam e se esses estados de coisas se mantêm.

Bem, isso não está totalmente certo. Os filósofos da linguagem comum procuravam responder a questões filosóficas observando nosso uso de frases. Um slogan do filósofo da linguagem comum é: uso é significado. Este filósofo passa a maior parte do tempo no nível dois, o nível da frase. Eles fazem perguntas como: minha frase tem sentido? Comunicará o que eu quero nos contextos relevantes? Será que outros usuários de idiomas vão entender? E o mais importante, eles respondem a questões filosóficas observando como a linguagem é usada. Essencialmente, o programador que passa a maior parte do tempo no nível da representação e execução concretas é como o filósofo da linguagem comum. Eles não estão preocupados com o que o código representa abstratamente. Eles só querem que dê certo!

Na filosofia, a abordagem da linguagem comum é controversa. Embora vários debates filosóficos e quebra-cabeças têm a aparência de jogos de linguagem. Para citar alguns:

  • O Navio de Teseu: se removermos e substituirmos completamente as partes de um navio ao longo de um ano, o navio original persiste ou há um novo navio no final?
  • Enigmas sobre a indefinição: se eu retiro lentamente o cabelo da cabeça de uma pessoa, há um momento exato em que ela está careca?
  • Homem do pântano: se existisse magicamente uma criatura que tivesse todas as mesmas propriedades estruturais que você, ela teria crenças sobre o mundo? Ou as crenças exigem uma história causal?
  • Composição: As partículas dispostas em forma de tabela formam alguma coisa adicional nova, uma tabela?

O filósofo da linguagem comum acredita que precisamos responder (ou desarmar) essas perguntas analisando a linguagem que as cria. Embora eu seja simpático a pensar que cada uma dessas perguntas não é substantiva, essa é uma peça diferente.

Mais importante, essa controvérsia não existe no software. Algumas questões filosóficas podem dizer respeito apenas à linguagem, mas os programas são tipicamente sobre o mundo concreto. Programadores escrevem aplicativos para movimentar dinheiro, analisar comportamento, transmitir fala, mover pessoas de a para b e muito mais.

Programação como Metafísica

Ted Sider abre Escrevendo O Livro do Mundo com a seguinte imagem:

Há duas maneiras de dividir este mundo: como um quadrado com dois lados vermelho e azul ou como um quadrado com dois lados diagonal esquerda e diagonal direita:

Dividir o mundo em diagonal esquerda e diagonal direita é estranho. No entanto, há um sentido em que não se estaria cometendo um erro: você pode descrever com precisão o quadrado por referência a seus dois lados, diagonal esquerda e diagonal direita. Não se estaria cometendo um erro no nível de proposições ou frases.

No entanto, a segunda imagem não “esculpiu a realidade em suas articulações”. Não descreve adequadamente a estrutura do mundo. A estrutura do quadrado é adequadamente descrita por vermelho e azul; diagonal esquerda e diagonal direita são construções artificiais. Outra maneira de colocar isso é que a diagonal esquerda e direita não são as representações abstratas da direita.

Uma maneira de ver a metafísica é como o projeto de esculpir a realidade em suas articulações. Metafísicos buscam teorias que descrevam a estrutura, a natureza ou as entidades fundamentais do mundo. Eles estão tentando desenvolver um relato do mundo que conceitualize adequadamente como ele é.

O design de software é o mesmo.

Voltemos ao aplicativo de meditação. Considere a pergunta, como devo representar uma meditação em um programa? Qual é a representação abstrata correta?

Um design simples seria algo parecido com o que já vimos:

interface Meditation {
  createdAt: DateTime,
  updatedAt: DateTime,
  courseId: Integer,
  title: String,
  description: String,
  filePath: String,
  meditationLengthSec: Integer,
}

Isso descreve as partes primárias de uma meditação como um objeto. Você pode imaginar carregá-los em um aplicativo e permitir que os usuários os reproduzam. No entanto, pode ser muito largo de um pincel. Ele combina áudio de meditação e uma meditação em uma única coisa - como você mudaria esse objeto se quisesse permitir que os usuários alterassem o comprimento ou o alto-falante de um alto-falante? Essa interface pressupõe que uma meditação tenha um único caminho ou comprimento de arquivo. No entanto, estas são propriedades de um , não de um . A introdução retroativa seria fácil no nível da interface, mas seria caro refatorar os consumidores que assumem que cada um tem apenas um.MeditationMeditationMeditationAudioMeditationMeditationAudiosMeditationMeditationAudio

Como o exemplo quadrado de Sider, você pode assumir que a interface original está correta. Se você quiser que os usuários possam alternar comprimentos, você pode até mesmo evitar criar com:MeditationAudio

interface Meditation {
  createdAt: DateTime,
  updatedAt: DateTime,
  courseId: Integer,
  title: String,
  description: String,
  filePath: String,
  meditationLengthSec: Integer,
  filePathII: String,
  meditationLengthSecII: Integer
}

Note que isso vai funcionar, mas parece deselegante. Ao tratar um objeto como algo para adicionar propriedades arbitrárias à ontologia do programa é irrestrito. Funciona, mas não está esculpindo o mundo em suas articulações, como o mundo diagonal. Uma maneira melhor de fazer isso seria distinguir explicitamente o de um objeto:MeditationMeditationMeditationAudio

interface Meditation {
  createdAt: DateTime,
  updatedAt: DateTime,
  courseId: Integer,
  title: String,
  description: String,
}

interface MeditationAudio {
  createdAt: DateTime,
  updatedAt: DateTime,
  meditationId: Integer,
  title: String,
  filePath: String,
  lengthSec: Integer,
  narratorId: Integer,
}

Agora que tem , o modelo de dados facilmente se estende para lidar com o caso em que uma única meditação tem vários arquivos de áudio ou narradores.MeditationsMeditationAudios

Considere dois outros exemplos. Suponha que você esteja criando um aplicativo de rastreamento de metas simples. Como metas e usuários devem ser relacionados? Depende do propósito do aplicativo, mas se for um aplicativo para indivíduos acompanharem seu progresso de forma privada, as metas devem pertencer a um único usuário. Agora suponha que você esteja criando o próximo aplicativo da fintech: você tem usuários e suas contas financeiras. Como relacionar usuários e contas? Inicialmente, a resposta pode ser a mesma; As contas devem pertencer a um único usuário. Mas isso não descreve adequadamente o que são contas financeiras. Pode haver contas conjuntas! Provavelmente, no início do ciclo de vida da sua startup, você pode andar de skate sem eles. Mais tarde, vai custar-lhe tempo e dinheiro para mudar.

O resultado disso é que o design de código não está apenas fazendo com que os elementos internos de um programa se encaixem. Trata-se de conceituar corretamente os objetos com os quais você está trabalhando. Fazer isso bem evita a dor do programador, economiza dinheiro e pode ser sublime. O objeto do design é o mundo, não apenas o código. Programação como metafísica.


Leia a versão de ciência da computação dessas ideias aqui: Os Três Níveis de Software de Jimmy Koppel. Recomendado.

Obrigado a Elaine Lin, Jimmy Koppel, Austin Wilson, Will Larson e companheiros do On Deck Writer Fellowpela leitura de versões anteriores deste ensaio.


Artigo Original