Maneira mais limpa de escrever software de procedimento lógico em um idioma OO


12

Sou engenheiro eletricista e não sei o que diabos estou fazendo. Salve os futuros mantenedores do meu código.

Recentemente, tenho trabalhado em vários programas menores (em C #) cuja funcionalidade é logicamente "processual". Por exemplo, um deles é um programa que coleta informações de bancos de dados diferentes, usa essas informações para gerar uma espécie de página de resumo, imprime e sai.

A lógica necessária para tudo isso é de cerca de 2000 linhas. Eu certamente não quero encher tudo isso de uma só vez main()e depois "limpá-lo" com #regions, como alguns desenvolvedores anteriores estão fazendo (estremecer).

Aqui estão algumas coisas que eu já tentei sem muita satisfação:

Crie utilitários estáticos para cada funcionalidade grosseira, por exemplo, DatabaseInfoGetter, SummaryPageGenerator e PrintUtility. Fazendo a função principal parecer com:

int main()
{
    var thisThing = DatabaseInfoGetter.GetThis();
    var thatThing = DatabaseInfoGetter.GetThat();
    var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

    PrintUtility.Print(summary);
}

Para um programa eu até fui com interfaces

int main()
{
    /* pardon the psuedocode */

    List<Function> toDoList = new List<Function>();
    toDoList.Add(new DatabaseInfoGetter(serverUrl));
    toDoList.Add(new SummaryPageGenerator());
    toDoList.Add(new PrintUtility());

    foreach (Function f in toDoList)
        f.Do();
}

Nada disso parece certo. À medida que o código aumenta, ambas as abordagens começam a ficar feias.

Qual é uma boa maneira de estruturar coisas assim?


2
Não tenho certeza de que isso seja estritamente uma duplicata, mas leia Método único com muitos parâmetros versus muitos métodos que devem ser chamados em ordem .


@ Snowman, como alguém que é novo em escrever trechos de código maiores, é reconfortante ver que eu não sou a única pessoa que se depara com esses tipos de problemas. Gostei da sua resposta a essa pergunta. Isso ajuda.
Lombard

@ Lombard, a idéia de reduzir a complexidade a um nível gerenciável é uma habilidade muito boa de se desenvolver. Ninguém pode entender uma grande base de código, nem mesmo o Super-Homem (talvez o Batman). Encontrar maneiras de expressar idéias de maneira simples e auto-documentar o código é crucial.

"Recentemente, tenho trabalhado em vários programas menores (em C #) cuja funcionalidade é logicamente" procedural ". Por exemplo, um deles é um programa que coleta informações de diferentes bancos de dados, usa essas informações para gerar uma espécie de resumo página, imprime e sai. ": Tem certeza de que não está confundindo" processual "com" imperativo "? Tanto o procedimento quanto o orientado a objetos são (podem ser) imperativos, ou seja, eles podem expressar operações a serem executadas em uma sequência.
Giorgio

Respostas:


18

Como você não é um programador profissional, eu recomendaria manter a simplicidade. Será muito mais fácil para o programador pegar seu código de procedimento modularizado e torná-lo OO mais tarde, do que para eles consertar um programa OO mal escrito. Se você não tem experiência, é possível criar programas de OO que podem se transformar em uma bagunça profana que não ajudará você nem quem quer que venha depois de você.

Eu acho que o seu primeiro instinto, o código "essa coisa - aquela coisa" no primeiro exemplo é o caminho certo. É claro e óbvio o que você quer fazer. Não se preocupe muito com a eficiência do código, a clareza é muito mais importante.

Se um segmento de código for muito longo, divida-o em pedaços pequenos, cada um com sua própria função. Se for muito curto, considere usar menos módulos e colocar mais em linha.

---- Postscript: Armadilhas de design OO

Trabalhar com sucesso com a programação OO pode ser complicado. Existem até pessoas que consideram todo o modelo defeituoso. Há um livro muito bom que eu usei quando aprendi a programação OO chamada Thinking in Java (agora em sua 4ª edição). O mesmo autor tem um livro correspondente para C ++. Na verdade, há outra questão sobre os programadores que lidam com armadilhas comuns na programação orientada a objetos .

Algumas armadilhas são sofisticadas, mas existem muitas maneiras de criar problemas de maneiras muito básicas. Por exemplo, alguns anos atrás, havia um estagiário na minha empresa que escreveu a primeira versão de algum software que eu herdei e ele fez interfaces para tudo o que pudesseum dia ter várias implementações. Obviamente, em 98% dos casos, havia apenas uma única implementação, então o código foi carregado com interfaces não utilizadas, o que tornou a depuração muito irritante porque você não pode voltar atrás em uma chamada de interface, então acaba fazendo uma pesquisa de texto para a implementação (embora agora eu uso o IntelliJ was tenha o recurso "Mostrar todas as implementações", mas antigamente eu não o tinha). A regra aqui é a mesma da programação procedural: sempre codifique uma coisa. Somente quando você tiver duas ou mais coisas, crie uma abstração.

Um tipo semelhante de erro de design pode ser encontrado na API Java Swing. Eles usam um modelo de publicação / assinatura para o sistema de menus Swing. Isso torna a criação e a depuração de menus no Swing um pesadelo completo. A ironia é que é completamente inútil. Praticamente nunca existe uma situação em que várias funções precisem "assinar" um clique no menu. Além disso, a publicação-assinatura foi um erro de ignição completo, porque normalmente um sistema de menus está sempre em uso. Não é como se as funções estivessem se inscrevendo e depois cancelando a inscrição aleatoriamente. O fato de os desenvolvedores "profissionais" da Sun terem cometido um erro como esse mostra apenas como é fácil para profissionais fazerem erros monumentais no design de OO.

Sou um desenvolvedor muito experiente, com décadas de experiência em programação OO, mas mesmo assim eu seria o primeiro a admitir que há toneladas que não conheço e mesmo agora sou muito cauteloso ao usar muito OO. Eu costumava ouvir longas palestras de um colega de trabalho que era um fanático por OO sobre como fazer projetos específicos. Ele realmente sabia o que estava fazendo, mas com toda a honestidade tive dificuldade em entender seus programas porque eles tinham modelos de design tão sofisticados.


1
+1 para "a clareza é muito mais importante [que a eficiência]"
Stephen

Você poderia me indicar um link que descreva o que constitui um "programa de OO mal escrito" ou adicione mais informações sobre isso em uma edição?
Lombard

@ Lombard eu fiz isso.
precisa

1

A primeira abordagem está bem. Em linguagens procedurais, como C, a abordagem padrão é dividir a funcionalidade em espaços para nome - usar classes estáticas como DatabaseInfoGetteré basicamente a mesma coisa. Obviamente, essa abordagem leva a um código mais simples, mais modular e mais legível / sustentável do que colocar tudo em uma classe, mesmo se tudo estiver dividido em métodos.

Falando nisso, tente limitar os métodos para executar o mínimo de ações possível. Alguns programadores preferem um pouco menos de granularidade, mas métodos enormes são sempre considerados prejudiciais.

Se você ainda está enfrentando dificuldades com a complexidade, provavelmente precisará interromper ainda mais o programa. Crie mais níveis na hierarquia - talvez DatabaseInfoGetterprecise fazer referência a outras classes, como ProductTableEntryalgo assim. Você está escrevendo código processual, mas lembre-se, você está usando C # e o OOP fornece várias ferramentas para reduzir a complexidade, por exemplo:

int main() 
{
    var thisthat = Database.GetThisThat();
}

public class ThisThatClass
{
    public String This;
    public String That;
}

public static class Database 
{
    public ThisThatClass GetThisThat()
    {
        return new ThisThatClass 
        {
            This = GetThis(),
            That = GetThat()
        };
    }

    public static String GetThis() 
    { 
        //stuff
    }
    public static String GetThat() 
    { 
        //stuff
    }

}

Além disso, tenha cuidado com as classes estáticas. As classes de banco de dados são boas candidatas. Contanto que você normalmente tenha um banco de dados, você entendeu a idéia.

Por fim, se você pensa em funções matemáticas e o C # não se adequa ao seu estilo, tente algo como Scala, Haskell etc. Eles também são legais.


0

Estou respondendo com 4 anos de atraso, mas quando você está tentando executar procedimentos em uma linguagem OO, está trabalhando em um antipadrão. Isso não é algo que você deve fazer! Encontrei um blog que aborda esse antipadrão e mostra uma solução para ele, mas requer muita refatoração e uma mudança de mentalidade. Consulte Código de procedimento em Código orientado a objetos para obter mais detalhes.
Como você mencionou "isto" e "aquilo", você está basicamente sugerindo que você tem duas classes diferentes. A Esta classe (mau nome!) E Essa classe. E você poderia, por exemplo, traduzir isso:

var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

nisso:

var summary = SummaryPageGenerator.GeneratePage(new This(DatabaseInfoGetter), new That(DatabaseInfoGetter));

Agora, seria interessante ver essas 2.000 linhas de código processual e determinar como várias partes poderiam ser agrupadas em classes separadas e mover vários procedimentos para essas classes.
Cada aula deve ser simples e focada em apenas uma coisa. E parece-me que algumas partes do código já estão lidando com superclasses, que na verdade são grandes demais para serem úteis. O DatabaseInfoGetter, por exemplo, parece confuso. O que está ficando assim? Parece muito genérico. No entanto, SummaryPageGenerator é muito específico! E o PrintUtility está sendo genérico novamente.
Portanto, para começar, evite os nomes genéricos para suas classes e comece a usar nomes mais específicos. A classe PrintUtility, por exemplo, seria mais uma classe Print com uma classe Header, uma classe Footer, uma classe TextBlock, uma classe Image e, possivelmente, uma maneira de indicar como elas fluiriam na impressão propriamente dita. Namespace PrintUtility , no entanto.
Para o banco de dados, história semelhante. Mesmo se você usar o Entity Framework para acesso ao banco de dados, ele deve ser limitado às coisas que você usa e dividido em grupos lógicos.
Mas eu me pergunto se você ainda está lidando com esse problema após 4 anos. Nesse caso, é uma mentalidade que precisa ser alterada do "conceito processual" para o "conceito orientado a objetos". O que é desafiador se você não tem a experiência ...


-3

Na minha opinião, você deve alterar seu código para que seja simples, consistente e legível.

Escrevi algumas postagens de blog sobre esse tópico e confie em mim: elas são simples de entender: o que é um bom código

Simples: assim como uma placa de circuito contém peças diferentes, cada uma com uma responsabilidade. O código deve ser dividido em partes menores e mais simples.

Consistente: use padrões, um padrão para nomear elementos e coisas. Você gosta de ferramentas que funcionam da mesma maneira o tempo todo e deseja saber onde encontrá-las. É o mesmo com o código.

Legível: não use siglas, a menos que sejam amplamente usadas e conhecidas no domínio que o software está alvejando (mattnz), prefira nomes pronunciáveis ​​que sejam claros e fáceis de entender.


1
Acrônimos são aceitáveis ​​se forem amplamente usados ​​e conhecidos no domínio que o software está direcionando. Tente escrever o software de controle de tráfego aéreo sem usar o - nós temos siglas compostas por siglas - ninguém saberia do que você estava falando, se você usou o nome completo. Coisas como HTTP para redes, ABS no setor automotivo etc são completamente aceitáveis.
218158 Mathews #
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.