c# - programacion - referencia microsoft csharp
Cita necesaria: el uso del preprocesador es una mala práctica de OO (14)
Creo que el uso de directivas de preprocesador como #if UsingNetwork
es una mala práctica de OO, otros compañeros de trabajo no lo hacen. Creo que cuando se utiliza un contenedor IoC (por ejemplo, Spring), los componentes se pueden configurar fácilmente si se programan en consecuencia. En este contexto, el contenedor IoC puede establecer una IsUsingNetwork
o, si la implementación de "using network" se comporta de manera diferente, se debe implementar e inyectar otra implementación de esa interfaz (por ejemplo: IService
, ServiceImplementation
, NetworkingServiceImplementation
).
¿Puede alguien proporcionar citas de OO-Gurus o referencias en libros que básicamente dicen "El uso del preprocesador es una mala práctica de OO si intentas configurar el comportamiento que debería configurarse a través de un contenedor IoC"?
Necesito estas citas para convencer a los compañeros de trabajo de refactorizar ...
Editar: sí sé y estoy de acuerdo en que el uso de directivas de preprocesador para cambiar el código específico de la plataforma de destino durante la compilación está bien y para eso están las directivas del preprocesador. Sin embargo, creo que se debe usar la configuración en tiempo de ejecución en lugar de la configuración compiletime para obtener clases y componentes diseñados y comprobables. En otras palabras: Usar #defines y # if''s más allá de lo que significan dará lugar a códigos difíciles de probar y clases mal diseñadas.
¿Alguien ha leído algo en esta línea y me puede dar para que pueda referirme?
"El preprocesador es la encarnación del mal y la causa de todo el dolor en la tierra" -Robert (OO Guru)
El soporte del preprocesamiento en C # es muy mínimo ... casi inútil. ¿Es eso el mal?
¿El preprocesador tiene algo que ver con OO? Seguramente es para la configuración de construcción.
Por ejemplo, tengo una versión lite y una versión pro de mi aplicación. Es posible que desee excluir algún código en la lite sin tener que recurrir a la construcción de versiones muy similares del código.
Puede que no desee enviar una versión lite, que es la versión pro con diferentes indicadores de tiempo de ejecución.
Tony
En mi humilde opinión, hablas de C y C ++, no de la práctica de OO en general. Y C no está orientado a objetos. En ambos idiomas, el preprocesador es realmente útil. Solo úsalo correctamente.
Creo que esta respuesta pertenece a C ++ FAQ: [29.8] ¿Estás diciendo que el preprocesador es malo? .
Sí, eso es exactamente lo que estoy diciendo: el preprocesador es malo.
Cada macro
#define
crea efectivamente una nueva palabra clave en cada archivo fuente y en cada alcance hasta que ese símbolo sea#undef
d. El preprocesador le permite crear un símbolo #define que siempre se reemplaza independientemente del{...}
ámbito donde aparece ese símbolo.A veces necesitamos el preprocesador, como el contenedor de
#ifndef/#define
dentro de cada archivo de encabezado, pero se debe evitar cuando se pueda. "Mal" no significa "nunca usar". A veces usarás cosas malvadas, particularmente cuando son "el menor de dos males". Pero todavía son malvados :-)
Espero que esta fuente sea lo suficientemente autorizada :-)
Las directivas de preprocesador en C # tienen casos de uso muy definidos y prácticos. Los que estás hablando específicamente, llamados directivas condicionales, se usan para controlar qué partes del código se compilan y cuáles no.
Hay una diferencia muy importante entre no compilar partes de código y controlar cómo se conecta el gráfico de objetos a través de IoC. Miremos un ejemplo del mundo real: XNA. Cuando está desarrollando juegos XNA que planea implementar tanto en Windows como en XBox 360, su solución generalmente tendrá al menos dos plataformas entre las que puede cambiar, en su IDE. Habrá varias diferencias entre ellos, pero una de esas diferencias será que la plataforma XBox 360 definirá un símbolo condicional XBOX360 que puede usar en su código fuente con el siguiente modismo:
#if (XBOX360)
// some XBOX360-specific code here
#else
// some Windows-specific code here
#endif
Por supuesto, podría restar importancia a estas diferencias utilizando un patrón de diseño de Estrategia y controlar a través de IoC, que se instanciará, pero la compilación condicional ofrece al menos tres ventajas principales:
- Usted no envía el código que no necesita.
- Puede ver las diferencias entre el código específico de plataforma para ambas plataformas en el contexto legítimo de ese código.
- No hay indirección por encima. El código apropiado está compilado, el otro no, y eso es todo.
La inyección de código del preprocesador es para el compilador lo que los disparadores son para la base de datos. Y es bastante fácil encontrar tales afirmaciones sobre los factores desencadenantes.
Pienso principalmente en #define que se usa para alinear una expresión corta porque guarda la sobrecarga de una llamada a función. En otras palabras, es una optimización prematura.
OMI es importante diferenciar entre #if y #define. Ambos pueden ser útiles y ambos pueden ser utilizados en exceso. Mi experiencia es que es más probable que #define se use en exceso que #if.
Pasé más de 10 años haciendo programación C y C ++. En los proyectos en los que trabajé (software disponible comercialmente para DOS / Unix / Macintosh / Windows) utilizamos #if y #define principalmente para tratar problemas de portabilidad de código.
Pasé suficiente tiempo trabajando con C ++ / MFC para aprender a detestar #define cuando se usa en exceso, lo que creo que es el caso en MFC alrededor de 1996.
Luego pasé más de 7 años trabajando en proyectos de Java. No puedo decir que eché de menos al preprocesador (aunque sin duda extrañé cosas como tipos enumerados y plantillas / genéricos que Java no tenía en ese momento).
He estado trabajando en C # desde 2003. Hemos hecho un uso intensivo de #if y [Conditional ("DEBUG")] para nuestras compilaciones de depuración, pero #if es simplemente una forma más conveniente y ligeramente más eficiente de hacer lo mismo cosas que hicimos en Java.
En el futuro, hemos comenzado a preparar nuestro motor principal para Silverlight. Si bien todo lo que hacemos se puede hacer sin #if, es menos trabajo con #if, lo que significa que podemos dedicar más tiempo a agregar funciones que nuestros clientes están solicitando. Por ejemplo, tenemos una clase de valor que encapsula un color de sistema para el almacenamiento en nuestro motor central. A continuación se encuentran las primeras líneas de código. Debido a la similitud entre System.Drawing.Color y System.Windows.Media.Color, el #define en la parte superior nos proporciona una gran cantidad de funcionalidades en .NET normal y en Silverlight sin duplicar el código:
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
#if SILVERLIGHT
using SystemColor = System.Windows.Media.Color;
#else
using SystemColor = System.Drawing.Color;
#endif
namespace SpreadsheetGear.Drawing
{
/// <summary>
/// Represents a Color in the SpreadsheetGear API and provides implicit conversion operators to and from System.Drawing.Color and / or System.Windows.Media.Color.
/// </summary>
public struct Color
{
public override string ToString()
{
//return string.Format("Color({0}, {1}, {2})", R, G, B);
return _color.ToString();
}
public override bool Equals(object obj)
{
return (obj is Color && (this == (Color)obj))
|| (obj is SystemColor && (_color == (SystemColor)obj));
}
...
Para mí, la conclusión es que hay muchas funciones de lenguaje que se pueden usar en exceso, pero esta no es razón suficiente para dejar estas funciones fuera o para establecer reglas estrictas que prohíban su uso. Debo decir que pasar a C # después de programar en Java durante tanto tiempo me ayuda a apreciar esto porque Microsoft (Anders Hejlsberg) ha estado más dispuesto a proporcionar características que pueden no atraer a un profesor universitario, pero que me hacen más productivo en mi trabajo y finalmente me permite construir un mejor widget en el tiempo limitado que tiene cualquiera con una fecha de envío.
Bjarne Stroustrap proporciona su respuesta (en general, no específica para IoC) aquí
Entonces, ¿qué hay de malo con el uso de macros?
(extracto)
Las macros no obedecen el alcance de C ++ y las reglas de tipo. Esto a menudo es la causa de problemas sutiles y no tan sutiles. En consecuencia, C ++ proporciona alternativas que se ajustan mejor al resto de C ++, como funciones en línea, plantillas y espacios de nombres.
Un punto rápido para decirle a sus compañeros de trabajo es este: el preprocesador rompe la precedencia del operador en las declaraciones matemáticas si se usan símbolos en tales declaraciones.
Usar un #if en lugar de un IoC o algún otro mecanismo para controlar diferentes funcionalidades basadas en la configuración probablemente sea una violación del Principio de Responsabilidad Individual, que es la clave para los diseños ''modernos'' de OO. Aquí hay una extensa serie de artículos sobre los principios de diseño de OO.
Como las partes en las diferentes secciones del #if por definición se refieren a diferentes aspectos del sistema, ahora está acoplando los detalles de implementación de al menos dos componentes diferentes en la cadena de dependencia de su código que usa el #if.
Al refactorizar esas preocupaciones, ha creado una clase que, suponiendo que esté terminada y probada, ya no será necesario abrirla, a menos que se rompa el código común.
En su caso original, deberá recordar la existencia del #if y tenerlo en cuenta cada vez que cualquiera de los tres componentes cambie con respecto a los posibles efectos secundarios de un cambio de rotura.
Un problema con el preprocesador # ifdef es que duplican eficazmente el número de versiones compiladas que, en teoría, debe probar exhaustivamente para poder decir que el código entregado es correcto.
#ifdef DEBUG
//...
#else
//...
Ok, ahora puedo producir la versión "Debug" y la versión "Release". Esto está bien para mí, siempre lo hago, porque tengo aserciones y seguimientos de depuración que solo se ejecutan en la versión de depuración.
Si alguien viene y escribe (ejemplo de la vida real)
#ifdef MANUALLY_MANAGED_MEMORY
...
Y escriben una optimización de mascotas que propagan a cuatro o cinco clases diferentes, y de repente tienes CUATRO maneras posibles de compilar tu código.
Si solo tiene otro código # ifdef-dependent, entonces tendrá OCHO versiones posibles para generar, y lo que es más inquietante, CUATRO de ellas serán posibles versiones de lanzamiento.
Por supuesto, en tiempo de ejecución if (), como bucles y lo que sea, cree ramas que debe probar, pero me resulta mucho más difícil garantizar que cada variación de tiempo de compilación de la configuración se mantenga correcta.
Esta es la razón por la que creo que, como política, todos los # ifdef excepto el de la versión Debug / Release deben ser temporales , es decir, estás haciendo un experimento en código de desarrollo y decidirás, pronto, si se mantiene de una manera o el otro
No tengo en mente ninguna afirmación de gurú con respecto al uso de directivas de preprocesador y no puedo agregar una referencia a una famosa. Pero quiero darle un enlace a una muestra simple que se encuentra en MSDN de Microsoft.
#define A
#undef B
class C
{
#if A
void F() {}
#else
void G() {}
#endif
#if B
void H() {}
#else
void I() {}
#endif
}
Esto dará como resultado el simple
class C
{
void F() {}
void I() {}
}
y creo que no es muy fácil de leer porque hay que mirar arriba para ver qué se define exactamente en este punto. Esto se está volviendo más complejo si lo ha definido en otro lugar.
Para mí, parece mucho más simple crear diferentes implementaciones e inyectarlas en un llamador en lugar de cambiar las definiciones para crear "nuevas" definiciones de clase. (... y debido a esto entiendo por qué se compara el uso de las definiciones de preprocesador con el uso de IoC en su lugar). Además de la horrible legibilidad del código usando instrucciones de preprocesador, rara vez utilicé las definiciones de preprocesador porque aumentan la complejidad de probar el código porque dan lugar a múltiples rutas (pero este es un problema de tener implementaciones múltiples inyectadas también por IoC-Container externo).
Microsoft mismo ha usado muchas definiciones de preprocesador dentro de la API de Win32 y es posible que usted sepa / recuerde el feo cambio entre las llamadas de método char y w_char.
Tal vez no deberías decir "No lo uses". Dígales "Cómo usarlo" y "Cuándo usarlo" en su lugar. Creo que todos estarán de acuerdo contigo si se te ocurre una alternativa buena (mejor comprensible) y puedes describir los riesgos del uso de preprocesador / makros.
No hay necesidad de un gurú ... solo sé un gurú. ;-)
Quería hacer una nueva pregunta, pero parece que encaja aquí. Estoy de acuerdo en que tener un preprocesador completo puede ser demasiado para Java. Hay una necesidad clara que está cubierta por el precoprocesador en el mundo C y no está cubierta en absoluto en el mundo Java: quiero que el compilador ignore por completo las impresiones de depuración dependiendo del nivel de depuración. Ahora confiamos en las "buenas prácticas", pero en la práctica esta práctica es difícil de aplicar y aún queda algo de carga de CPU redundante.
En estilo Java que podría resolverse teniendo algunos métodos designados como debug (), warning () etc. para los que se llama al código se genera condicionalidad.
En realidad, eso se trataría de un poco de integración de Log4J en el lenguaje.
En c # / VB.NET no diría que es malvado.
Por ejemplo, al depurar servicios de Windows, pongo lo siguiente en Principal para que cuando esté en modo Depuración, pueda ejecutar el servicio como una aplicación.
<MTAThread()> _
<System.Diagnostics.DebuggerNonUserCode()> _
Shared Sub Main()
#If DEBUG Then
''Starts this up as an application.''
Dim _service As New DispatchService()
_service.ServiceStartupMethod(Nothing)
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite)
#Else
''Runs as a service. ''
Dim ServicesToRun() As System.ServiceProcess.ServiceBase
ServicesToRun = New System.ServiceProcess.ServiceBase() {New DispatchService}
System.ServiceProcess.ServiceBase.Run(ServicesToRun)
#End If
End Sub
Esto está configurando el comportamiento de la aplicación, y ciertamente no es malo. Por lo menos, no es tan malo como intentar depurar una rutina de inicio de servicio.
Corrígeme si leo mal tu OP, pero parece que te estás quejando de que otros usen un preprocesador cuando un simple booleano sería suficiente. Si ese es el caso, no jodas a los preprocesadores, malditos sean los que los usen de esa manera.
EDITAR: Re: primer comentario. No entiendo cómo se relaciona ese ejemplo aquí. El problema es que el preprocesador está siendo mal usado, no que sea malo.
Te daré otro ejemplo. Tenemos una aplicación que hace la verificación de versiones entre el cliente y el servidor al inicio. En desarrollo, a menudo tenemos diferentes versiones y no queremos hacer una verificación de versión. ¿Es esto malo?
Creo que lo que estoy tratando de decir es que el preprocesador no es malo, incluso cuando se cambia el comportamiento del programa. El problema es que alguien está abusando de él. ¿Qué hay de malo en decir eso? ¿Por qué intentas descartar una función de idioma?
Mucho más tarde EDIT : FWIW: no he usado directivas de preprocesador para esto en unos pocos años. Utilizo Environment.UserInteractive con un conjunto de argumentos específico ("-c" = Console), y un truco ingenioso que recogí de aquí en SO para no bloquear la aplicación, pero aún esperar la entrada del usuario.
Shared Sub Main(args as string[])
If (Environment.UserInteractive = True) Then
''Starts this up as an application.
If (args.Length > 0 AndAlso args[0].Equals("-c")) Then ''usually a "DeriveCommand" function that returns an enum or something similar
Dim isRunning As Boolean = true
Dim _service As New DispatchService()
_service.ServiceStartupMethod(Nothing)
Console.WriteLine("Press ESC to stop running")
While (IsRunning)
While (Not (Console.KeyAvailable AndAlso (Console.ReadKey(true).Key.Equals(ConsoleKey.Escape) OrElse Console.ReadKey(true).Key.Equals(ConsoleKey.R))))
Thread.Sleep(1)
End While
Console.WriteLine()
Console.WriteLine("Press ESC to continue running or any other key to continue shutdown.")
Dim key = Console.ReadKey();
if (key.Key.Equals(ConsoleKey.Escape))
{
Console.WriteLine("Press ESC to load shutdown menu.")
Continue
}
isRunning = false
End While
_service.ServiceStopMethod()
Else
Throw New ArgumentException("Dont be a double clicker, this needs a console for reporting info and errors")
End If
Else
''Runs as a service. ''
Dim ServicesToRun() As System.ServiceProcess.ServiceBase
ServicesToRun = New System.ServiceProcess.ServiceBase() {New DispatchService}
System.ServiceProcess.ServiceBase.Run(ServicesToRun)
End If
End Sub
Henry Spencer escribió un artículo llamado #ifdef Considerado dañino .
Además, el propio Bjarne Stroustrup, en el capítulo 18 de su libro The Design and Evolution of C ++ , frunce el ceño ante el uso del preprocesador y desea eliminarlo por completo. Sin embargo, Stroustrup también reconoce la necesidad de la directiva #ifdef y la compilación condicional y continúa para ilustrar que no hay una buena alternativa para ella en C ++.
Finalmente, Pete Goodliffe, en el capítulo 13 de su libro Code Craft: The Practice of Writing Excellent Code , da un ejemplo de cómo, incluso cuando se usa para su propósito original, #ifdef puede desordenar tu código.
Espero que esto ayude. Sin embargo, si sus compañeros de trabajo no van a escuchar argumentos razonables en primer lugar, dudo que las citas de los libros los ayuden a convencerlos;)