Una forma más limpia de escribir software lógicamente procesal en un lenguaje OO

12

Soy ingeniero eléctrico y no sé qué demonios estoy haciendo. Guarde los futuros mantenedores de mi código.

Recientemente he estado trabajando en una serie de programas más pequeños (en C #) cuya funcionalidad es lógicamente "procesal". Por ejemplo, uno de ellos es un programa que recopila información de diferentes bases de datos, usa esa información para generar una especie de página de resumen, la imprime y luego sale.

La lógica requerida para todo eso es alrededor de 2000 líneas. Ciertamente no quiero meter todo eso en un solo main()y luego "limpiarlo" con #regions como han estado haciendo algunos desarrolladores anteriores (estremecimiento).

Aquí hay algunas cosas que ya he probado sin mucha satisfacción:

Cree utilidades estáticas para cada bit grueso de funcionalidad, por ejemplo, DatabaseInfoGetter, SummaryPageGenerator y PrintUtility. Hacer que la función principal se vea así:

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

    PrintUtility.Print(summary);
}

Para un programa incluso fui con 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 de esto se siente bien. A medida que el código se alarga, ambos enfoques comienzan a ponerse feos.

¿Cuál es una buena manera de estructurar cosas como esta?

Lombardo
fuente
2
No estoy seguro de que esto sea estrictamente un duplicado, pero lea Método único con muchos parámetros frente a muchos métodos que deben llamarse en orden .
@Snowman, como alguien que es nuevo en escribir piezas de código más grandes, es tranquilizador ver que no soy la única persona que se encuentra con este tipo de problemas. Me gustó tu respuesta a esa pregunta. Ayuda.
Lombard
@Lombard, la idea de reducir la complejidad a un nivel manejable es una muy buena habilidad para desarrollar. Nadie puede entender una base de código grande, ni siquiera Superman (quizás Batman). Encontrar formas de expresar ideas de manera simple y auto documentar el código es crucial.
"Recientemente he estado trabajando en varios programas más pequeños (en C #) cuya funcionalidad es lógicamente" procesal ". Por ejemplo, uno de ellos es un programa que recopila información de diferentes bases de datos, usa esa información para generar una especie de resumen página, lo imprime y luego sale. ": ¿Está seguro de que no está confundiendo" procedimiento "con" imperativo "? Tanto los procedimientos como los orientados a objetos son (pueden ser) imperativos, es decir, pueden expresar operaciones que se realizarán en una secuencia.
Giorgio

Respuestas:

18

Como usted no es un programador profesional, le recomendaría mantener la simplicidad. Será mucho más fácil para el programador tomar su código de procedimiento modularizado y hacer que sea OO más tarde, que será para ellos arreglar un programa OO mal escrito. Si no tienes experiencia, es posible crear programas de OO que pueden convertirse en un desastre impuro que no te ayudará a ti ni a quien te persiga.

Creo que su primer instinto, el código "esta cosa-esa cosa" en el primer ejemplo es el camino correcto. Es claro y obvio lo que quieres hacer. No se preocupe demasiado por la eficiencia del código, la claridad es mucho más importante.

Si un segmento de código es demasiado largo, divídalo en trozos pequeños, cada uno con su propia función. Si es demasiado corto, considere usar menos módulos y poner más en línea.

---- Postscript: trampas de diseño OO

Trabajar con éxito con la programación OO puede ser complicado. Incluso hay algunas personas que consideran que todo el modelo tiene fallas. Hay un muy buen libro que utilicé cuando aprendí por primera vez la programación OO llamado Pensamiento en Java (ahora en su cuarta edición). El mismo autor tiene un libro correspondiente para C ++. En realidad, hay otra pregunta sobre los programadores que se enfrentan a dificultades comunes en la programación orientada a objetos .

Algunas trampas son sofisticadas, pero hay muchas maneras de crear problemas de manera muy básica. Por ejemplo, hace un par de años hubo un pasante en mi empresa que escribió la primera versión de un software que heredé e hizo interfaces para todo lo que pudieraAlgún día tendrá múltiples implementaciones. Por supuesto, en el 98% de los casos solo hubo una única implementación, por lo que el código se cargó con interfaces no utilizadas, lo que hizo que la depuración fuera muy molesta porque no puede retroceder a través de una llamada de interfaz, por lo que termina teniendo que hacer un búsqueda de texto para la implementación (aunque ahora uso IntelliJ tenía la función "Mostrar todas las implementaciones", pero en el pasado no tenía eso). La regla aquí es la misma que para la programación de procedimientos: siempre codificar una cosa. Solo cuando tenga dos o más cosas, cree una abstracción.

Un tipo similar de error de diseño se puede encontrar en la API Java Swing. Utilizan un modelo de publicación-suscripción para el sistema de menús Swing. Esto hace que crear y depurar menús en Swing sea una pesadilla completa. La ironía es que es completamente inútil. Prácticamente nunca existe una situación en la que múltiples funciones necesiten "suscribirse" a un clic en el menú. Además, publicar-suscribir fue una falla total porque un sistema de menús normalmente siempre está en uso. No es que las funciones se suscriban y luego se den de baja al azar. El hecho de que los desarrolladores "profesionales" de Sun hayan cometido un error como este simplemente muestra lo fácil que es incluso para los profesionales cometer errores monumentales en el diseño OO.

Soy un desarrollador muy experto con décadas de experiencia en programación OO, pero incluso yo sería el primero en admitir que hay toneladas que no conozco e incluso ahora soy muy cauteloso sobre el uso de una gran cantidad de OO. Solía ​​escuchar largas conferencias de un compañero de trabajo que era un fanático de OO sobre cómo hacer diseños particulares. Realmente sabía lo que estaba haciendo, pero honestamente, me costó entender sus programas porque tenían modelos de diseño tan sofisticados.

Tyler Durden
fuente
1
+1 para "la claridad es mucho más importante [que la eficiencia]"
Stephen
¿Podría indicarme un enlace que describa lo que constituye un "programa OO mal escrito" o agregar más información al respecto en una edición?
Lombard
@Lombard Lo hice.
Tyler Durden
1

El primer enfoque está bien. En lenguajes de procedimiento, como C, el enfoque estándar es dividir la funcionalidad en espacios de nombres; usar clases estáticas como DatabaseInfoGetteres básicamente lo mismo. Obviamente, este enfoque conduce a un código más simple, más modular y más legible / mantenible que poner todo en una clase, incluso si todo se divide en métodos.

Hablando de eso, trate de limitar los métodos para realizar la menor cantidad de acciones posible. Algunos programadores prefieren un poco menos de granularidad, pero los métodos enormes siempre se consideran dañinos.

Si todavía está luchando con la complejidad, lo más probable es que necesite desglosar su programa aún más. Cree más niveles en la jerarquía; tal vez DatabaseInfoGetternecesite hacer referencia a otras clases como ProductTableEntryo algo así. Está escribiendo código de procedimiento, pero recuerde, ESTÁ usando C #, y OOP le brinda un montón de herramientas para reducir la complejidad, por ejemplo:

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
    }

}

Además, tenga cuidado con las clases estáticas. Las clases de bases de datos son buenas candidatas ... siempre que tenga una sola base de datos ... se le ocurre la idea.

En última instancia, si piensa en funciones matemáticas y C # no se adapta a su estilo, pruebe algo como Scala, Haskell, etc. También son geniales.

Escepticismo
fuente
0

Estoy respondiendo 4 años tarde, pero cuando intentas hacer cosas de procedimiento en un lenguaje OO, entonces estás trabajando en un antipatrón. ¡Eso no es algo que debas hacer! Encontré un blog que aborda este antipatrón y muestra una solución para él, pero requiere mucha refactorización y un cambio de mentalidad. Consulte Código de procedimiento en Código orientado a objetos para obtener más detalles.
Como mencionaste "esto" y "aquello", básicamente estás sugiriendo que tienes dos clases diferentes. A Esta clase (¡mal nombre!) Y Esa clase. Y podría, por ejemplo, traducir esto:

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

dentro de esto:

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

Ahora, sería interesante ver esas más de 2,000 líneas de código de procedimiento y determinar cómo se pueden agrupar varias partes en clases separadas y mover varios procedimientos hacia abajo en esas clases.
Cada clase debe ser simple y centrada en hacer una sola cosa. Y me parece que algunas partes del código ya están tratando con superclases, que en realidad son demasiado grandes para ser útiles. DatabaseInfoGetter, por ejemplo, parece confuso. ¿Qué está recibiendo de todos modos? Suena demasiado genérico. ¡Sin embargo, SummaryPageGenerator es terriblemente específico! Y PrintUtility está siendo genérico nuevamente.
Entonces, para comenzar, debe evitar los nombres genéricos para sus clases y comenzar a usar nombres más específicos. La clase PrintUtility, por ejemplo, sería más una clase Print con una clase Header, una clase Footer, una clase TextBlock, una clase Image y posiblemente alguna forma de indicar cómo fluirían en la impresión misma. Todo esto podría estar en el Sin embargo, el espacio de nombres PrintUtility .
Para la base de datos, historia similar. Incluso si usa Entity Framework para el acceso a la base de datos, debe limitarlo a las cosas que usa y dividirlo en grupos lógicos.
Pero me pregunto si todavía estás lidiando con este problema después de 4 años. Si es así, entonces es una mentalidad que necesita ser cambiada del "concepto procesal" al "concepto orientado a objetos". Lo cual es desafiante si no tienes la experiencia ...

Wim ten Brink
fuente
-3

Soy mi opinión, debe cambiar su código para que sea simple, consistente y legible.

Escribí algunas publicaciones de blog sobre este tema, y ​​confía en mí, son fáciles de entender: ¿Qué es un buen código?

Simple: al igual que una placa de circuito contiene diferentes piezas, cada una con una responsabilidad. El código debe dividirse en partes más pequeñas y simples.

Consistente: use patrones, un estándar para nombrar elementos y cosas. Te gustan las herramientas que funcionan de la misma manera todo el tiempo y quieres saber dónde encontrarlas. Es lo mismo con el código.

Legible: no use siglas a menos que sean ampliamente utilizadas y conocidas dentro del dominio al que se dirige el software (mattnz), prefiera nombres pronunciables que sean claros y fáciles de entender.

Pablo Granados
fuente
1
Los acrónimos son aceptables si se usan ampliamente y se conocen dentro del dominio al que se dirige el software. Intente escribir el software de Control de tráfico aéreo sin usar el - tenemos acrónimos formados por acrónimos - nadie sabría de qué estaba hablando si usa el nombre completo. Cosas como HTTP para redes, ABS en automoción, etc. son completamente aceptables.
mattnz