Un olor a código es un síntoma que indica que hay un problema en el diseño que potencialmente aumentará el número de errores: este no es el caso para las regiones, pero las regiones pueden contribuir a crear olores de código, como los métodos largos.
Ya que:
Un antipatrón (o antipatrón) es un patrón utilizado en operaciones sociales o comerciales o ingeniería de software que puede usarse comúnmente pero que es ineficaz y / o contraproducente en la práctica.
Las regiones son antipatrones. Requieren más trabajo que no aumenta la calidad o la legibilidad del código, que no reduce la cantidad de errores y que solo puede hacer que el código sea más complicado de refactorizar.
No use regiones dentro de los métodos; refactorizar en su lugar
Los métodos deben ser cortos . Si solo hay diez líneas en un método, probablemente no usaría regiones para ocultar cinco de ellas cuando trabaje en otras cinco.
Además, cada método debe hacer una sola cosa . Las regiones, por otro lado, están destinadas a separar cosas diferentes . Si su método hace A, entonces B, es lógico crear dos regiones, pero este es un enfoque incorrecto; en su lugar, debe refactorizar el método en dos métodos separados.
El uso de regiones en este caso también puede dificultar la refactorización. Imagina que tienes:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
if (!verification)
{
throw new DataCorruptedException();
}
Do(data);
DoSomethingElse(data);
#endregion
#region Audit
var auditEngine = InitializeAuditEngine();
auditEngine.Submit(data);
#endregion
}
El colapso de la primera región para concentrarse en la segunda no solo es arriesgado: podemos olvidarnos fácilmente de la excepción que detiene el flujo (podría haber una cláusula de protección con una return
, que es aún más difícil de detectar), sino que también tendría un problema si el código debe ser refactorizado de esta manera:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
var info = DoSomethingElse(data);
if (verification)
{
Do(data);
}
#endregion
#region Audit
var auditEngine = InitializeAuditEngine(info);
auditEngine.Submit(
verification ? new AcceptedDataAudit(data) : new CorruptedDataAudit(data));
#endregion
}
Ahora, las regiones no tienen sentido, y no es posible leer y comprender el código en la segunda región sin mirar el código en la primera.
Otro caso que a veces veo es este:
public void DoSomething(string a, int b)
{
#region Validation of arguments
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
#endregion
#region Do real work
...
#endregion
}
Es tentador usar regiones cuando la validación de argumentos comienza a abarcar decenas de LOC, pero hay una mejor manera de resolver este problema: la utilizada por el código fuente de .NET Framework:
public void DoSomething(string a, int b)
{
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
InternalDoSomething(a, b);
}
private void InternalDoSomething(string a, int b)
{
...
}
No use regiones fuera de los métodos para agrupar
Algunas personas los usan para agrupar campos, propiedades, etc. Este enfoque es incorrecto: si su código es compatible con StyleCop, entonces los campos, propiedades, métodos privados, constructores, etc. ya están agrupados y son fáciles de encontrar. Si no es así, es hora de comenzar a pensar en aplicar reglas que garanticen la uniformidad en su base de código.
Otras personas usan regiones para ocultar muchas entidades similares . Por ejemplo, cuando tiene una clase con cien campos (que genera al menos 500 líneas de código si cuenta los comentarios y el espacio en blanco), puede verse tentado a colocar esos campos dentro de una región, colapsarlos y olvidarse de ellos. Nuevamente, lo estás haciendo mal: con tantos campos en una clase, deberías pensar mejor en usar la herencia o cortar el objeto en varios objetos.
Finalmente, algunas personas se sienten tentadas a usar regiones para agrupar cosas relacionadas : un evento con su delegado, o un método relacionado con IO con otros métodos relacionados con IO, etc. En el primer caso, se convierte en un desastre que es difícil de mantener , Lea y entienda. En el segundo caso, el mejor diseño probablemente sería crear varias clases.
¿Hay un buen uso para las regiones?
No. Hubo un uso heredado: código generado. Aún así, las herramientas de generación de código solo tienen que usar clases parciales en su lugar. Si C # tiene soporte de regiones, es principalmente porque este uso heredado, y porque ahora que demasiadas personas usan regiones en su código, sería imposible eliminarlas sin romper las bases de código existentes.
Piensa en ello como en goto
. El hecho de que el idioma o el IDE admitan una función no significa que deba usarse a diario. La regla StyleCop SA1124 es clara: no debe usar regiones. Nunca.
Ejemplos
Actualmente estoy haciendo una revisión del código del código de mi compañero de trabajo. La base de código contiene muchas regiones, y en realidad es un ejemplo perfecto de cómo no usar regiones y por qué las regiones conducen a un código incorrecto. Aquí hay unos ejemplos:
4 000 monstruo LOC:
Recientemente leí en algún lugar de Programmers.SE que cuando un archivo contiene demasiados using
s (después de ejecutar el comando "Eliminar usos no utilizados"), es una buena señal de que la clase dentro de este archivo está haciendo demasiado. Lo mismo se aplica al tamaño del archivo en sí.
Mientras revisaba el código, me encontré con un archivo de 4 000 LOC. Parecía que el autor de este código simplemente copiaba y pegaba el mismo método de 15 líneas cientos de veces, cambiando ligeramente los nombres de las variables y el método llamado. Una expresión regular simple permitió recortar el archivo de 4 000 LOC a 500 LOC, simplemente agregando algunos genéricos; Estoy bastante seguro de que con una refactorización más inteligente, esta clase puede reducirse a unas pocas docenas de líneas.
Al usar regiones, el autor se animó a ignorar el hecho de que el código es imposible de mantener y está mal escrito, y a duplicar en gran medida el código en lugar de refactorizarlo.
Región "Do A", Región "Do B":
Otro excelente ejemplo fue un método de inicialización de monstruos que simplemente realizó la tarea 1, luego la tarea 2, luego la tarea 3, etc. Hubo cinco o seis tareas que fueron totalmente independientes, cada una inicializando algo en una clase contenedor. Todas esas tareas se agruparon en un método y se agruparon en regiones.
Esto tenía una ventaja:
- El método fue bastante claro de entender al observar los nombres de las regiones. Dicho esto, el mismo método una vez refactorizado sería tan claro como el original.
Los problemas, por otro lado, fueron múltiples:
No era obvio si había dependencias entre las regiones. Con suerte, no hubo reutilización de variables; de lo contrario, el mantenimiento podría ser una pesadilla aún más.
El método era casi imposible de probar. ¿Cómo podría saber fácilmente si el método que hace veinte cosas a la vez las hace correctamente?
Región de campos, región de propiedades, región de constructor:
El código revisado también contenía muchas regiones que agrupaban todos los campos, todas las propiedades, etc. Esto tenía un problema obvio: el crecimiento del código fuente.
Cuando abre un archivo y ve una lista enorme de campos, está más inclinado a refactorizar la clase primero y luego trabajar con el código. Con las regiones, tienes el hábito de colapsar cosas y olvidarte de ellas.
Otro problema es que si lo haces en todas partes, te encontrarás creando regiones de un bloque, lo que no tiene ningún sentido. Este fue realmente el caso en el código que revisé, donde había muchos que #region Constructor
contenían un constructor.
Finalmente, los campos, propiedades, constructores, etc. ya deberían estar en orden . Si lo son y coinciden con las convenciones (constantes que comienzan con una letra mayúscula, etc.), ya está claro en qué parte del tipo de elementos se detiene y comienza otro, por lo que no es necesario crear regiones explícitamente para eso.