O código está vivo e morto. Parte I Os objetos

Código é um pensamento. Um problema surge e o desenvolvedor pensa em como resolvê-lo, como expressar os requisitos em funções e classes, como torná-los amigos, como obter rigor e correção e como vingar um negócio. Práticas, técnicas, princípios, padrões e abordagens - tudo precisa ser levado em consideração e tudo precisa ser lembrado.


E, junto com isso, vemos uma epidemia generalizada de gerentes, ajudantes, serviços, controladores, seletores, adaptadores, getters, levantadores e outros espíritos malignos: tudo isso é código morto. Ele grilhões e bagunça.


Proponho que se lute dessa maneira: você precisa apresentar os programas como texto em um idioma natural e avaliá-los de acordo. Como isso e o que acontece - no artigo.


Sumário do ciclo


  1. Os objetos
  2. Ações e propriedades
  3. Código como texto

Prólogo


Minha experiência é modesta (cerca de quatro anos), mas quanto mais trabalho, mais entendo: se o programa é ilegível, não faz sentido nele. Há muito que é conhecido e criticado lembrar - o código não apenas resolve alguns problemas agora , mas também mais tarde : é suportado, expandido, corrigido. Além disso, ele sempre: leia.


Afinal, isso é texto.


A estética do código como texto é um tema-chave no ciclo. A estética aqui é um vidro através do qual olhamos as coisas e dizemos: sim, é bom, sim, é lindo.


Em questões de beleza e compreensibilidade, as palavras são de grande importância. Dizer: "No momento, minhas percepções estão em um estado de tédio devido ao alto nível de etanol no sangue" não é o mesmo que: "Fiquei bêbado" .


Temos sorte que os programas são quase inteiramente compostos de palavras.


Digamos que você precise criar um "personagem que tenha saúde e mana, ele anda, ataca, usa feitiços" e você pode ver imediatamente: existem objetos (personagem, saúde, mana, feitiço), ações (caminhar, atacar, usar) e propriedades ( o personagem tem saúde, mana, velocidade de lançar feitiços) - todos serão nomes: classes, funções, métodos, variáveis, propriedades e campos, em uma palavra, tudo em que a linguagem de programação se divide.


Mas não vou distinguir classes de estruturas, campos de propriedades e métodos de funções: o personagem como parte da narrativa não depende de detalhes técnicos (que pode ser representado por referência ou por tipo significativo). Uma coisa essencialmente diferente: que este é um personagem e que o chamavam de Hero (ou Character ), e não HeroData ou HeroUtils .


Agora vou pegar o próprio copo da estética e mostrar como algum código é escrito hoje e por que está longe de ser perfeito.


Os objetos


Em C # (e não apenas), objetos - instâncias de classes que são colocadas na pilha, ficam lá por um tempo e, em seguida, o coletor de lixo as remove. Também podem ser criadas estruturas na pilha ou em matrizes associativas, ou qualquer outra coisa. Para nós, eles são: nomes de classes, substantivos.


Nomes no código, como nomes em geral, podem ser confusos. Sim, e você raramente vê um nome feio, mas um objeto bonito. Especialmente se for um Manager .


Gerente em vez de um objeto


UserService , AccountManager , DamageUtils , MathHelper , GraphicsManager , GameManager , VectorUtil .


Não é a precisão e a tangibilidade que domina aqui, mas algo vago, deixando em algum lugar na neblina. Para tais nomes, muito é permitido.


Por exemplo, em qualquer GameManager você pode adicionar qualquer coisa relacionada ao jogo e à lógica do jogo . Seis meses depois, virá uma singularidade tecnológica.


Ou, acontece, você precisa trabalhar com o facebook. Por que não colocar todo o código em um só lugar: FacebookManager ou FacebookService ? Parece sedutoramente simples, mas uma intenção tão vaga cria uma decisão igualmente vaga. Ao mesmo tempo, sabemos que existem usuários, amigos, mensagens, grupos, músicas, interesses etc. no Facebook. Chega de palavras!


Não existem apenas palavras suficientes: ainda as usamos. Somente no discurso comum, não entre programas.


E não é o GitUtils , mas o IRepository , ICommit , IBranch ; não ExcelHelper , mas ExcelDocument , ExcelSheet ; não GoogleDocsService , mas GoogleDocs .


Cada área de assunto é preenchida com objetos. “Os objetos foram marcados por grandes vazios” , “O coração estava batendo furiosamente” , “A casa estava em pé” - os objetos agem, eles sentem, são fáceis de imaginar; eles estão em algum lugar aqui, tangíveis e densos.


Junto com isso, você às vezes vê isso: no repositório da Microsoft/calculator - CalculatorManager com os métodos: SetPrimaryDisplay , MaxDigitsReached , SetParentDisplayText , OnHistoryItemAdded ...


(Ainda assim, lembro que uma vez vi o UtilsManager ...)


Isso acontece da seguinte maneira: eu quero expandir o tipo List<> com um novo comportamento, e ListUtils ou ListHelper nascem. Nesse caso, é melhor e mais preciso usar apenas métodos de extensão - ListExtensions : eles fazem parte do conceito e não um despejo dos procedimentos.


Uma das poucas exceções é o OfficeManager como uma postagem.


De resto ... Os programas não devem ser compilados se contiverem essas palavras.


Ação em vez de objeto


IProcessor , ILoader , ISelector , IFilter , IProvider , ISetter , ICreator , IOpener , IHandler ; IEnableable , IEnableable , IUpdatable , ICloneable , IDrawable , ILoadable , IOpenable , ISettable , IConvertible .


Aqui, a essência da essência é um procedimento, não um conceito, e o código novamente perde sua imagem e legibilidade, e a palavra usual é substituída por uma artificial.


Muito mais animado é ISequence , não IEnumerable ; IBlueprint , não ICreator ; IButton , não IButtonPainter ; IPredicate , não IFilter ; IGate , não IOpeneable ; IToggle , não IEnableable .


Uma boa história conta sobre os personagens e seu desenvolvimento, e não sobre como o criador cria, o construtor constrói e o pintor desenha. Uma ação não pode representar totalmente um objeto. ListSorter não é um SortedList .


Veja o DirectoryCleaner , por exemplo, um objeto que limpa as pastas no sistema de arquivos. É elegante? Mas nunca dizemos: “Peça ao limpador de pastas para limpar D: / Test” , sempre: “Limpe D: / Test” , para que o Directory com o método Clean pareça mais natural e mais próximo.


Um caso mais animado é mais interessante: o FileSystemWatcher do .NET é um observador do sistema de arquivos que relata alterações. Mas por que todo o observador, se as próprias mudanças podem relatar que aconteceram? Além disso, eles devem estar indissociavelmente vinculados ao arquivo ou pasta, para que também sejam colocados no Directory ou no File (usando a propriedade Changes com a capacidade de chamar file.Changes.OnNext(action) ).


Tais nomes verbais parecem justificar o padrão de design da Strategy , instruindo a "encapsular a família de algoritmos" . Mas se, em vez de uma "família de algoritmos", encontrarmos um objeto autêntico que existe na história, veremos que a estratégia é apenas uma generalização.


Para explicar esses e muitos outros erros, passamos à filosofia.


A existência precede a essência


MethodInfo , ItemData , AttackOutput , CreationStrategy , StringBuilder , SomethingWrapper , LogBehaviour .


Tais nomes estão unidos por uma coisa: seu ser é baseado em detalhes.


Acontece que algo interfere rapidamente em uma tarefa: algo está faltando ou está, mas não algo. Então você pensa: “Uma coisa que pode fazer X agora me ajudaria” - é assim que a existência é concebida. Então, para "fazer" X, XImpl é gravado - é assim que a entidade aparece.


Portanto, em vez de IArrayItem , IIndexedItem ou IItemWithIndex é mais comum ou, digamos, na API de reflexão, em vez do método ( Method ), vemos apenas informações sobre ele ( MethodInfo ).


Uma maneira mais verdadeira: diante da necessidade de existência, encontre uma entidade que a implemente e, como essa é a sua natureza, outras.


Por exemplo, eles queriam alterar o valor do tipo string sem criar instâncias intermediárias - acabou sendo uma solução direta na forma de StringBuilder , enquanto, na minha opinião, é mais apropriado - MutableString .


Lembre-se do sistema de arquivos: DirectoryRenamer não DirectoryRenamer necessário para renomear pastas, porque assim que você concorda com a presença do objeto Directory , a ação está nele, você simplesmente não encontrou o método correspondente no código.


Se você deseja descrever como bloquear , não é necessário esclarecer que isso é ILockBehaviour ou ILockStrategy , o que é muito mais fácil - ILock (com o método Acquire que retorna IDisposable ) ou ICriticalSection (com Enter ).


Isso também inclui todos os tipos de Data , Info , Output , Input , Args , Args (menos frequentemente State ) - objetos que são completamente desprovidos de comportamento, porque foram considerados unilaterais.


Onde a existência é primária, o quociente é confundido com o geral, e os nomes dos objetos são confusos - você precisa ler cada linha e descobrir para onde o personagem foi e por que existem apenas os Data dele.


Taxonomia Bizarra


CalculatorImpl , AbstractHero , ConcreteThing , CharacterBase .


Pelas mesmas razões descritas acima, às vezes vemos objetos para os quais um lugar na hierarquia é indicado com precisão. Mais uma vez, a existência avança, novamente vemos como a necessidade imediata foi rapidamente derramada no código, sem considerar as consequências.


Afinal, existe realmente uma pessoa ( Human ) - o herdeiro da pessoa base ( HumanBase )? Mas como é quando o Item herda AbstractItem ?


Às vezes, eles querem mostrar que não há Character , mas algum tipo de semelhança "bruta" - CharacterRaw .


Impl , Abstract , Custom , Base , Concrete , Internal , Raw - um sinal de instabilidade, imprecisão da arquitetura que, como a arma da primeira cena, certamente disparará mais tarde.


Repetições


Com tipos aninhados, isso acontece: RepositoryItem no Repository , WindowState na Window , HeroBuilder no Hero .


A repetição corta o significado, exacerba falhas e apenas contribui para a complexidade do texto.


Peças redundantes


Para sincronizar threads, o ManualResetEvent é frequentemente usado com a seguinte API:


 public class ManualResetEvent { //   —  `EventWaitHandle`. void Set(); void Reset(); bool WaitOne(); } 

Sempre que, pessoalmente, tenho que lembrar como o Set difere da Reset (gramática desconfortável) e o que é um "evento de redefinição manual" em geral no contexto de trabalho com fluxos.


Nesses casos, é mais fácil usar metáforas longe da programação (mas próximas à vida cotidiana):


 public class ThreadGate { void Open(); void Close(); bool WaitForOpen(); } 

Certamente não há nada a ser confundido!


Às vezes, é ridículo: especifique que os itens não são apenas Items , mas necessariamente ItemsList ou ItemsDictionary !


No entanto, se o ItemsList não ItemsList engraçado, o AbstractInterceptorDrivenBeanDefinitionDecorator do Spring está ItemsList . As palavras neste nome são trapos dos quais um monstro gigantesco é costurado. Embora ... Se este é um monstro, o que é HasThisTypePatternTriedToSneakInSomeGenericOrParameterizedTypePatternMatchingStuffAnywhereVisitor ? Legado da esperança.


Além dos nomes de classe e interface, geralmente encontramos redundância em variáveis ​​ou campos.


Por exemplo, um campo do tipo IOrdersRepository é chamado _ordersRepository . Mas qual a importância de relatar que os pedidos são enviados pelo repositório? Afinal, muito mais fácil - _orders .


Também acontece que, nas consultas LINQ, eles escrevem os nomes completos dos argumentos das expressões lambda, por exemplo, Player.Items.Where(item => item.IsWeapon) , apesar de já entendermos esse item olhando para Player.Items . Nesses casos, eu gosto de sempre usar o mesmo caractere - x : Player.Items.Where(x => x.IsWeapon) (com continuação para y , z , se essas são funções dentro das funções).


Total


Admito que, com esse começo, não será fácil encontrar a verdade objetiva. Alguém, por exemplo, dirá: escrever Service ou não escrever é um ponto discutível, insignificante, bom gosto, e que diferença faz se funcionar?


Mas você pode beber de copos descartáveis!


Estou convencido de que o caminho para o conteúdo está no formulário e, se alguém não olha para o pensamento, ele parece ter sumido. No texto do programa, tudo funciona da mesma maneira: estilo, atmosfera e ritmo ajudam a se expressar não confusos, mas compreensíveis e com capacidade.


O nome do objeto não é apenas o rosto, mas também o ser. Determina se será etéreo ou saturado, abstrato ou real, seco ou animado. O nome está mudando - o conteúdo está mudando.


No próximo artigo, falaremos sobre esse conteúdo e o que acontece.

Source: https://habr.com/ru/post/pt447404/


All Articles