O Ansel é projetado, não improvisado. Hackers podem gostar de “trabalhar” acelerando a morte do Darktable ao aumentar sua dívida técnica .
O que é projetar?
Projetar é um processo pelo qual você desenrola uma metodologia para trazer uma solução técnica ao problema de um ser humano. O processo de projeto tem como objetivo convergir para a solução mais adequada, ao mesmo tempo em que combate o impulso natural de se lançar sobre a primeira ideia ou a mais confortável.
Sem requisitos e projeto, programar é a arte de adicionar bugs a um arquivo de texto vazio. — Louis Srygley
- Projetar começa com um caso de uso: uma tarefa definida a realizar (sobre uma imagem), por um usuário definido, dentro de um prazo definido. Se não houver caso de uso, então não há problema a resolver, então mantenha distância do seu editor de código.
- Projetar exige conhecer o usuário-alvo: educação/formação, nível de habilidade/domínio, etc.
- Projetar exige compreender as necessidades: no contexto do Ansel, isso muitas vezes exigirá algum conhecimento de história da arte e de fotografia de sala escura,
- Uma vez compreendidos o problema e o usuário, projetar exige especificar:
- as funcionalidades esperadas da solução,
- o escopo da solução (em que ponto do ciclo de vida de uma imagem a solução se posiciona?),
- as restrições e requisitos da solução (suportar algum padrão, permitir processar n imagens por unidade de tempo, etc.),
- uma série de testes a completar que validariam a qualidade da solução, de modo a limitar opiniões improdutivas, vieses e subjetividade no processo de validação.
- Somente então podem começar os mockups e o brainstorming, seguidos pelos protótipos.
O que projetar não é?
Projetar não lida com:
- requisitos vagos de um usuário indefinido, futuro ou fantasiado,
- “seria legal se…” (é assim que se criam coleções inconsistentes de plugins),
- o que as pessoas gostam (para tudo que alguém gosta, você encontrará alguém para odiar),
- o que as pessoas acham que querem (frequentemente não é o que precisam),
- palavras da moda de tecnologia mágica que “são o futuro” e, como tais, precisam ser plugadas em todos os lugares, independentemente de sua relevância ou viabilidade (sim, estou falando de IA, NFT, blockchain, etc.)
O que é um bom projeto
Um bom projeto é:
- minimalista,
- robusto,
- à prova de futuro,
- genérico e generalizado,
- sustentável com recursos limitados,
- fundamentado pela ciência,
- compatível/interoperável com os padrões da indústria.
Como o Ansel é uma aplicação baseada em fluxo de trabalho, um bom projeto também atenta ao fluxo de trabalho como um todo, e onde o problema/solução se encaixa nele.
Como se faz um bom projeto?
Para auxiliar o processo de projeto, a comunicação deve permanecer concisa, focada, e as pessoas que participam desse processo devem garantir que têm uma compreensão adequada da teoria e do embasamento técnico envolvidos no escopo do problema/solução.
Convém enfatizar que, embora o projeto seja orientado por software, nem todas as soluções envolvem programação. Às vezes (frequentemente?), melhor educação ou melhor documentação é tudo o que se precisa.
O propósito de um processo de projeto saudável é evitar enviesar as soluções cedo demais com a tecnologia/projeto de estimação de alguém e evitar se perder nas tecnicalidades, mas sempre voltar aos fundamentos e princípios básicos daquilo que estamos fazendo: pós-processar lotes possivelmente grandes de imagens raw para todos os tipos de mídia de saída.
Isso é reforçado pelo fato de que os usuários raramente conhecem suas próprias necessidades, ou melhor, as necessidades que expressam raramente são a raiz do que realmente querem. A tarefa difícil de projetar é cortar através dos galhos para chegar à raiz, porque resolver o problema-raiz geralmente resulta em soluções mais elegantes, genéricas e minimalistas.
Os problemas vêm primeiro
O primeiro passo do processo de projeto do Ansel é submeter um pedido de recurso, na Comunidade. Os pedidos de recurso foram retirados do Github porque essa plataforma é inóspita para não programadores e não falantes de inglês (embora a Comunidade suporte apenas francês e inglês).
Esse pedido de recurso se concentrará no problema a resolver e se absterá de propor qualquer solução. O problema será definido em termos de tarefas a realizar no fluxo de trabalho de um fotógrafo ou do resultado visual esperado da imagem processada, ou seja, em termos do objetivo final a alcançar, não em termos de ferramentas ou tecnicalidades que se julga necessárias. Isso pode levar a uma discussão para escavar até as raízes do problema, que geralmente estão bem escondidas sob aquilo que o usuário pensa ser o seu problema .
Nenhuma proposta de solução é aceita nesta etapa.
As soluções vêm em segundo
Quando a definição e o escopo do problema forem acordados entre as pessoas envolvidas na discussão, soluções podem ser propostas. Mais discussão pode ser necessária para avaliar as desvantagens e os benefícios de cada solução, levando à adoção, por princípio, da melhor solução. As soluções são definidas por suas funcionalidades (ou seja, o que devem fazer), não por sua tecnologia ou meios (como devem fazê-lo).
Nenhuma proposta de protótipo é aceita nesta etapa.
As soluções adotadas levarão a uma nova issue sendo triada no quadro Kanban de gerenciamento do projeto .
Elas podem estar condicionadas à pesquisa de aspectos teóricos e técnicos para avaliar sua viabilidade, caso em que serão triadas na coluna To research do quadro Kanban. Os resultados da pesquisa serão adicionados à issue original até que a viabilidade da solução seja comprovada. Quando for, a issue será movida para a coluna “To do” do quadro Kanban.
Soluções adotadas podem ser triadas diretamente para a coluna To do se exigirem apenas ferramentas e tecnologias bem conhecidas.
Idealmente, os pontos a testar e o procedimento de teste para validar o protótipo deveriam ser escritos antes mesmo de se ter um protótipo funcional. No mínimo, os testes deveriam garantir que nenhuma regressão ocorreu nos recursos e ferramentas relacionados.
Os protótipos vêm em terceiro
Somente as issues triadas na coluna “To do” do quadro Kanban de gerenciamento do projeto serão trabalhadas, por mim ou por qualquer pessoa disposta a enfrentá-las.
O protótipo da solução será proposto em um pull request de um branch de tópico vinculando a issue original. Branches de tópico precisam ser rebaseados sobre o branch master, por exemplo git rebase ustream master ou, se você atualizar seu branch localmente com novos commits do master, faça git pull upstream master --rebase ou configure o git globalmente para fazer pull via rebase em vez de merge. Isso garante que o histórico do seu branch seja mantido limpo com esforço mínimo, e mantém o histórico do master limpo também quando seu PR é mesclado.
Quando o pull request do protótipo é revisado e se ele atende aos padrões de qualidade de código (veja abaixo) ao mesmo tempo em que atende às especificações da solução adotada, ele é aprovado e automaticamente triado para a coluna “To test/validating” do quadro Kanban de gerenciamento do projeto .
A validação vem em quarto
Pull requests aprovados serão mesclados cedo no branch candidate ou dev para teste, dependendo se podem quebrar os históricos de edição de imagens (ao adicionar novos parâmetros de módulo ou alterar o esquema do banco de dados). Esse branch será sempre o branch master com todos os pull requests pendentes de validação por cima. Isso tem como objetivo ajudar nos testes por parte de pessoas que não estão necessariamente familiarizadas com a mesclagem manual de branches do git. Ao contrário do branch dev, o candidate não deve quebrar suas edições.
Se nenhum bug ou quebra for relatado após algum tempo e o protótipo cumprir seu propósito inicial corretamente, ele será mesclado no master e a issue relacionada será fechada e movida para a coluna “Done” do quadro Kanban de gerenciamento do projeto .
Se o protótipo se mostrar insatisfatório, ele pode ser rejeitado e outro precisará ser elaborado.
Dicas de profissional de um designer experiente
Nem todos os problemas de software são problemas de programação
Muitos problemas não exigem mais ferramentas (ou brinquedos) e mais código. Mais código é sempre ruim de qualquer forma, e deve ser evitado sempre que possível. Muito frequentemente, o problema do usuário é que ele não consegue ver como dobrar os recursos existentes para atender às suas necessidades. Isso é resolvido pela educação, ou seja, melhor documentação e mais tutoriais, e às vezes por uma interface melhor.
Ouça os usuários, mas não dê ouvidos a eles
Os usuários expressam o que querem e o que gostam, nunca o que precisam. E você não precisa ouvi-los para saber o que será:
- eles vão querer a mesma coisa que o vizinho acabou de ganhar,
- eles vão gostar daquilo a que estão acostumados.
E então, para tudo que alguém gosta, você encontrará outro para desgostar. Então a maneira Darktable de resolver conflitos é não resolver o conflito, mas dar a cada um uma opção, um modo, uma preferência para habilitar aquela coisa especial que eles gostam, do jeito que eles gostam. Isso significa mais case no seu switch, mais if aninhados, mais caminhos de código que você precisará testar agora, depurar e manter no futuro, e então mais preferências escondendo as outras na janela de preferências. Antes que você perceba, o código é um tumor que ninguém mais entende, e consertá-lo só o torna mais complicado.
Quando você arranha abaixo da superfície, descobre que aquilo de que as pessoas realmente precisam está muito mais próximo das necessidades das outras pessoas do que aquilo que dizem querer. Então você pode reconciliar as necessidades muito mais facilmente do que os desejos, e sem comprometer. Mas então você tem que rastrear as necessidades-raiz abaixo da vontade, e isso exige habilidades de abstração e psicologia.
Designers de interface são idiotas perigosos
Todo mundo que só enxerga, foca e se importa com a interface é um idiota perigoso. Se sua interface gráfica é complicada, isso significa muito mais do que apenas uma “interface complicada”: significa que a complexidade do seu backend chegou ao seu frontend. Descobri da maneira difícil que a complexidade da interface gráfica nunca é separada, e não pode ser resolvida separadamente, da complexidade do backend e da arquitetura geral da aplicação. A interface gráfica não é paralela à arquitetura do backend, ela é a terminação dela.
O problema dos designers de interface é que eles tipicamente não programam, ou, se programam, são péssimos em programação de baixo nível e arquitetura de software. Então eles focam no pouco que veem e entendem (típico efeito do poste de luz ), e só produzem designs não acionáveis que conflitam com aquilo de que o software realmente precisa para funcionar. Porque essa interface gráfica está apenas conectando a entrada do usuário ao backend, e se precisamos de tantos widgets, é porque a arquitetura precisa de tantas entradas. Você não escapa disso: para remover widgets, você precisa remover entradas, o que significa que sua arquitetura terá que funcionar com menos graus de liberdade primeiro. Isso começa com a simplificação do backend, o que significa uma refatoração fedorenta de código velho e empoeirado que ninguém mais entende.
Você não resolve problemas de interface gráfica com desenhos e mockups, você resolve problemas de interface gráfica resolvendo problemas de backend. Mas então você precisa de caras que entendam ambos os níveis, e eles podem ser caros demais para você.
Pergunte-se 36 vezes por dia qual era o problema que você estava tentando resolver
É super fácil se perder em tecnicalidades ao programar em uma linguagem de baixo nível e lutar contra bibliotecas ou APIs de terceiros, mas às vezes a solução é simples e elegante e você se deixou levar longe demais para dentro de ponteiros e travas de thread. Sempre volte ao problema inicial em questão, essa é sua linha de vida para a simplicidade.
Qual é o problema? Quem o enfrenta? Quando? Com que frequência? Fazendo o quê?
O melhor caminho é o caminho mais simples em direção à sua solução: baixas tecnologias, pouco código, poucas camadas.
Documente seu projeto de merda
Muitas vezes, refiz completamente um projeto enquanto o documentava, porque é quando você tenta explicá-lo que percebe que ele é complicado demais para explicar, o que significa que é complicado demais para entender. Se você não consegue explicar seu projeto em alguns parágrafos, ou sua documentação tem “se isto, então aquilo” demais, geralmente há duas razões:
- sua interface gráfica não expõe a informação relevante onde o usuário precisa dela, então você tem que linkar metade da documentação na sua explicação para redirecionar os usuários a tudo o que precisam saber ou verificar antes de usar a única coisa que você estava documentando. A solução é trazer de volta a informação relevante para onde ela é necessária.
- sua interface gráfica tem bandejas, elementos recolhíveis, comportamentos contextuais, casos de uso ou preferências ocultas demais, e cobrir todas as bases faz você escrever um romance. A solução é linearizar o fluxo de trabalho, talvez remover opções ou dividir recursos.
A interface gráfica é como os usuários controlam o backend, mas também é onde eles aprendem sobre os recursos existentes e o que eles fazem. A documentação deve fornecer contexto, diretrizes e referências sobre como fazemos as coisas, mas a interface gráfica deve explicar o que ela mesma faz.
Claro, há uma limitação óbvia nisso: em uma aplicação de fotografia, os usuários precisam entender fotografia e sua linguagem, que envolve coisas como faixa dinâmica, gamut de cor, mapeamento de tons, etc. A interface gráfica deve ser autoexplicativa sobre como se supõe que ela deve ser usada, não remover a necessidade de aprender o ofício (o que deve ser feito e como).
Projetar é um processo iterativo
Uma aplicação é um mundo virtual no qual uma pequena mudança pode reordenar como o restante do ecossistema se adapta ao seu redor. Portanto, qualquer mudança de projeto pode desencadear a necessidade de mudar outras coisas ao redor (refatorar ferramentas, mover widgets, podar recursos). O que então pode desencadear a necessidade de corrigir a mudança inicial novamente. É um processo passo a passo no qual é tolice sequer tentar acertar tudo em cada passo, o que importa é que cada passo melhore o ambiente em relação ao anterior.
Às vezes, o (re)projeto não pode ser feito em pequenos passos, mas em grandes saltos: é quando você refaz a arquitetura. Esses saltos quebrarão muitas coisas ao seu redor, o que é aceitável se a arquitetura mais nova for mais simples e mais robusta no geral, e se você lhe der algum tempo para se recuperar antes de pegar a marreta novamente. Mas isso criará um estado transitório no qual o novo projeto parecerá pior do que o anterior. Isso nos diz que como o projeto é percebido não é uma entrada válida: a qualidade do projeto tem que ser avaliada em relação aos seus objetivos e medida com métricas objetivas, não com sentimentos e testes rápidos.
E às vezes, alguns passos são erros e deveriam ser revertidos. A falácia do custo irrecuperável não deveria ser usada para justificar que algum reprojeto deva ser mantido porque deu muito trabalho realizá-lo. É esperado que nem toda pesquisa e desenvolvimento chegue à produção.
Sessões de whack-a-mole significam que sua arquitetura chegou ao fim de sua vida útil
Quer você fique criando novos bugs enquanto conserta os antigos, ou fique criando casos extremos ao estender algum recurso, tudo aponta na mesma direção: sua arquitetura não pode mais ser dobrada porque ultrapassou seus requisitos de projeto. Pode ser que o backend tenha ficado convoluto demais ou pode ser que a arquitetura existente realmente não tenha sido planejada para o que você está tentando fazê-la fazer, mas, de ambas as formas, você terá que refazer a arquitetura e parar de improvisar no lugar. Caso contrário, você está apenas adicionando dívida técnica.
Mas então, o custo de desenvolvimento muda de escala e aquele projeto de tarde de sábado pode se tornar um projeto de um mês.
Boas práticas são diretrizes, não regras
Boas práticas ajudam a desenvolver hábitos saudáveis e código limpo, a menos que você não entenda o problema que elas tentaram resolver e as use fora de seu escopo de validade. Nesse caso, elas se tornam culto à carga: tentar imitar os efeitos na esperança de que isso conserte magicamente as causas também.
A primeira que vem à mente é o reúso/compartilhamento de código. Se reusar o mesmo código para recursos (aparentemente similares) leva a ramificações internas demais (if aninhados, switch/case, etc.), para lidar com todos os caminhos possíveis, o que você ganha em volume de código se perde em complexidade ciclomática, e, aliás, seus recursos não são tão similares quanto você pensava.
Além disso, duplicar código pode ser um ponto de partida para otimizar localmente a duplicata mais tarde: uma vez que você tenha o procedimento completo diante de você, pode identificar passos que podem ser armazenados em cache ou fatorados. Ao passo que, se o procedimento for apenas métodos de API opacos, de alto nível e reutilizáveis, então você perde a capacidade de identificar e remover computações redundantes. Portanto, existe um princípio de reúso/compartilhamento de dados (ou seja, armazenar em cache dados computados que serão usados mais tarde sem alteração, para poupar ciclos de CPU) que pode ser tornado impossível pelo reúso/compartilhamento de código, porque ele ofusca e abstrai o ciclo de vida dos dados.
Isso se torna crítico em loops de pixel: você quer colapsar todas as operações por pixel no mesmo loop, para pagar o preço de I/O de memória apenas uma vez. O que significa que você terá que reimplementar a mesma correção afim ($y = a * x + b$) em cada loop que a usa, em vez de ter um método reutilizável que faz justamente isso em seu próprio loop.
Não se contraia
Se você se encontra sobrecarregado por alguns bugs crípticos e aleatórios que não param de aparecer e dos quais você não consegue tirar sentido, não continue lutando às cegas e dê um passo atrás. Então instrumente auxiliares de depuração, ou gerenciadores de mais alto nível que acompanham os estados internos e lhe dão um mapa dos valores de dados do software em qualquer ponto relevante de seu ciclo de vida. Isso é especialmente crítico em configurações assíncronas, onde várias threads criam, acessam ou computam coisas em paralelo em linhas do tempo diferentes, e a sequência real de ordenação depende do contexto de tempo de execução.
Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.