Skip to content

Latest commit

 

History

History
5126 lines (3880 loc) · 194 KB

CleanABAP_es.md

File metadata and controls

5126 lines (3880 loc) · 194 KB

Clean ABAP

Español  ·  English  ·  中文  ·  Français  ·  Deutsch  ·  日本語  ·  한국어  ·  Русский

Esta guía fue adaptada en base a Clean Code por Robert C. Martin para ABAP.

La Cheat Sheet es una versión optimizada para impresión.

Contenido

Introducción

Clean ABAP > Contenido > Esta sección

Cómo comenzar a programar código limpio

Clean ABAP > Contenido > Introducción > Esta sección

Si eres nuevo al Código Limpio, deberías leer primero [ Código Limpio] (o Clean Code) por Robert C. Martin. La iniciativa Clean Code Developer puede ayudarte a comenzar con una introducción didáctica al tema en general.

Te recomendamos comenzar con cosas que sean fáciles de entender y ampliamente aceptadas, como Booleanos, Condiciones e Ifs.

Probablemente te beneficiarás más de la sección Métodos, especialmente Haz una cosa, hazla bien, solo haz eso y Mantén los métodos cortos, ya que mejoran tremendamente la estructura de tu código.

Algunos temas de esta guía pueden iniciar conversaciones difíciles en los equipos que tienen experiencia en lo que hacen, pero son nuevos al código limpio. Estos temas son perfectamente "sanos", pero la gente puede tener problemas adaptándose a ellos en el principio.

Una vez que domines los primeros temas, pasa a otros más controversiales; ya que especialmente Comentarios, Nomenclatura, y Formato pueden llevar a discusiones casi religiosas y solo deberían ser utilizadas por equipos que ya tengan prueba de los efectos positivos del código limpio.

Cómo refactorizar código legacy

Clean ABAP > Contenido > Introducción > Esta sección

Los temas Booleanos, Condiciones, Ifs, y Métodos son los que más te recompensan al trabajar con un proyecto legacy con mucho código que no puedes o no quieres cambiar, dado que puede ser aplicado solo al código nuevo sin conflicto.

El tema Nomenclatura es muy exigente para proyectos legacy, ya que puede introducir una fuerte diferencia entre código viejo y nuevo, al grado que secciones como Evita codificaciones, como notación Húngara y prefijos es mejor ignorarlas.

Trata de no mezclar estilos diferentes de programación dentro del mismo objeto de desarrollo al llevar a cabo refactorización. Si el código legacy contiene solo declaraciones al inicio y una refactorización completa en declaraciones in-line no es posible, es mejor mantener el estilo del código legacy que mezclar ambos estilos. Hay varias situaciones similares donde mezclar estilos puede causar confusión, por ejemplo:

  • Mezclar REF TO y FIELD-SYMBOL al hacer iteraciones.
  • Mezclar NEW y CREATE OBJECT al llamar un CONSTRUCTOR.
  • Mezclar RETURNING y EXPORTING en los prototipos que solo regresan / exportan un parámetro.

Hemos observado buenos resultados con un plan de cuatro pasos para refactorización:

  1. Convence al equipo. Comunica y explica el nuevo estilo y logra que todos en el equipo lo acepten. No necesitas comprometerte con toda la guía desde el inicio, solo comienza con un sub-conjunto de reglas indiscutibles y evoluciona a partir de eso.

  2. Sigue la regla de los boy scout en tu rutina de trabajo diaria: siempre deja el código un poco más limpio de lo que lo encontraste. No te obsesiones dedicando horas a "limpiar el campamento", solo dedica un par de minutos adicionales y observa como las mejoras se acumulan con el tiempo.

  3. Construye islas limpias: de vez en cuando, elige un pequeño objeto o componente y trata de hacerlo limpio en todos los sentidos. Estas islas demuestran el beneficio de lo que estás haciendo y forman una base sólidamente probada para hacer más refactorizaciones en el futuro.

  4. Habla de ello. No importa si usas Revisiones de código Fagan como antaño, llevas a cabo sesiones informativas o formas paneles de discusión en tu herramienta de chat favorita: vas a necesitar hablar de tus experiencias y aprendizajes, para habilitar que el equipo llegue a un entendimiento común.

Cómo revisar automáticamente

Clean ABAP > Contenido > Introducción > Esta sección

code pal para ABAP provee una suite comprensiva de revisiones automáticas para Clean ABAP.

ABAP Test Cockpit, Code Inspector, Extended Check y Checkman proveen algunas revisiones que te pueden ayudar a detectar ciertos problemas.

abapOpenChecks, es una colección Open Source de revisiones de Code Inspector, también cubre algunos anti-patrones.

abaplint es una reimplementación Open Source del parser de ABAP. Funciona sin un sistema SAP y está pensado para ser utilizado de manera serializada junto con abapGit. Ofrece múltiples integraciones (GitHub Actions, Jenkins, editores de texto...), cubre algunos de los anti-patrones y puede ser usado también para revisar formato y convenciones de código.

Cómo se relaciona con otras guías

Clean ABAP > Contenido > Introducción> Esta sección

Nuestra guía sigue el espiritú de Clean Codem lo que significa que hicimos algunos ajustes para el lenguaje de programación ABAP, por ejemplo Lanza CX_STATIC_CHECK para excepciones que se pueden manejar.

Algunos hechos provienen de la Guía para programación ABAP, con la cual esta guía es compatible en su mayor parte. Las desviaciones respecto a esa guía siempre están indicadas y son siempre con el objetivo de tener código más limpio.

Esta guía también respeta las Recomendaciones de DSAG para desarrollo ABAP, aunque es más precisa en la mayoría de los casos.

Desde su publicación, Clean ABAP se ha convertido en una guía de referencia para muchos de los equipos de desarrollo de SAP in-house, incluyendo los cientos de desarrolladores que trabajan en S/4HANA.

Cómo estar en desacuerdo

Clean ABAP > Contenido > Introducción > Esta sección

Escribimos esta guía de estilo para lectores que ya están familiarizados con Clean Code o que están en el proceso de hacerlo, con un enfoque específico en cómo aplicar código limpio específicamente para ABAP.

Por este motivo, por favor ten en cuenta que no introducimos todos los conceptos con el mismo detalle que el libro original y las fuentes mencionadas: estas fuentes aún vale la pena que sean leídas, especialmente si no estás de acuerdo con elementos de esta guía porque no los explicamos tan bien. Usa los enlaces en las secciones para leer el trasfondo de nuestra guía.

Eres libre de discutir y estar en desacuerdo con cualquier cosa que decimos aquí. Uno de los pilares de Clean Code es que el equipo manda. Solo asegúrate de darle a cada cosa una oportunidad justa antes de descartarla.

CONTRIBUTING.md sugiere maneras en que puedes cambiar esta guía o desviarte de ella en detalles menores. Toda contribución o discusión se lleva a cabo en inglés.

Nomenclatura

Clean ABAP > Contenido > Esta sección

Usa nombres descriptivos

Clean ABAP > Contenido > Nomenclatura > Esta sección

Usa nombres que muestren el contenido y significado de las cosas.

CONSTANTS max_wait_time_in_seconds TYPE i ...
DATA customizing_entries TYPE STANDARD TABLE ...
METHODS read_user_preferences ...
CLASS /clean/user_preference_reader ...

No te enfoques en el tipo de datos o codificación técnica. Rara vez contribuyen a entender el código.

" anti-pattern
CONSTANTS sysubrc_04 TYPE sysubrc ...
DATA iso3166tab TYPE STANDARD TABLE ...
METHODS read_t005 ...
CLASS /dirty/t005_reader ...

No trates de arreglar nombres malos con comentarios.

Lee más en el Capítulo 2: Nombres con sentido: Usar nombres que revelen las intenciones de [Código Limpio por Robert C. Martin].

Prefiere nombres del dominio de solución o de problema

Clean ABAP > Contenido > Nomenclatura > Esta sección

Busca nombres buenos en el dominio de solución, por ejemplo en ciencias de la computación términos como "pila" o "árbol"; y en el dominio de problema, por ejemplo "cuenta" o "libro_mayor".

Las capas que pertenecen a negocio cuando son nombradas de acuerdo al dominio de problema. Esto es especialmente cierto para componentes que son diseñados bajo el diseño guiado por el dominio, como las APIs y los objetos de negocio.

Las capas que proveen la mayor parte de la funcionalidad técnica, como las clases fábrica y algoritmos abstractos, sonarán mejor cuando son nombradas de acuerdo al dominio de solución.

En cualquier caso, no trates de hacer tu propio lenguaje. Necesitamos poder intercambiar información entre desarrolladores, product owners y clientes, así que elige nombres que todos entiendan sin requerir un diccionario a la medida.

Lee más en Capítulo 2: Nombres con sentido: Usar nombres de dominios de soluciones y [...]: Usar nombres de dominios de problemas de [Código Limpio por Robert C. Martin].

Usa plural

Clean ABAP > Contenido > Nomenclatura > Esta sección

Hay una práctica antigua en SAP en la que nombran las tablas de las entidades en singular, por ejemplo country para una "tabla de países". Una tendencia común en el mundo fuera de SAP es usar plural para listas de cosas. Por lo tanto, recomendamos elegir countries en su lugar.

Este consejo es principalmente para cosas como variables y propiedades. Para objetos de desarrollo, puede haber patrones que también tienen sentido, por ejemplo la convención ampliamente usada de nombrar tablas de bases de datos ("tablas transparentes") en singular.

Lee más en Capítulo 2: Nombres con sentido: Usar nombres que revelen las intenciones de [Código Limpio por Robert C. Martin].

Usa nombres pronunciables

Clean ABAP > Contenido > Nomenclatura > Esta sección

Pensamos y hablamos mucho de objetos, así que usa nombres que puedas pronunciar, por ejemplo, es preferible tipos_de_objeto_de_detección a algo ininteligible como tipobjdet

Lee más en Capítulo 2: Nombres con sentido: Usar nombres que se puedan pronunciar de [Código Limpio por Robert C. Martin]

Evita abreviaciones

Clean ABAP > Contenido > Nomenclatura > Esta sección

Si tienes suficiente espacio, escribe los nombres completos. Comienza a abreviar únicamente si excedes el límite de caracteres.

Si tienes que abreviar, comienza con las palabras con poca importancia.

Abreviar puede parece eficiente inicialmente, pero se vuelve ambiguo muy rápido. Por ejemplo, nombrar una variable como cust significa "customizing", "customer" o "custom"? Las tres son muy comunes en aplicaciones SAP.

Lee más en Capítulo 2: Nombres con sentido: Realizar distinciones con sentido de [Código Limpio por Robert C. Martin].

Usa las mismas abreviaciones en todas partes

Clean ABAP > Contenido > Nomenclatura > Esta sección

Las personas realizan búsquedas usando palabras clave para encontrar código relevante. Apoya esto usando la misma abreviación para el mismo concepto. Por ejemplo, siempre abrevia "tipo de objeto de detección" a "tipobjdet", en lugar de mezclar "tod", "tipod", "tipobjd", etc.

Lee más en Capítulo 2: Nombres con sentido: Usar nombres que se puedan buscar de [Código Limpio por Robert C. Martin].

Usa sustantivos para las clases y verbos para los métodos

Clean ABAP > Contenido > Nomenclatura > Esta sección

Usa sustantivos o frases con sustantivos para nombrar clases, interfaces y objetos:

CLASS /clean/account
CLASS /clean/user_preferences
INTERFACE /clean/customizing_reader

Usa verbos o frases de verbos para nombrar métodos:

METHODS withdraw
METHODS add_message
METHODS read_entries

Iniciar métodos booleanos con verbos como is_ y has provee un flujo de lectura agradable:

IF is_empty( table ).

Recomendamos nombrar las funciones como a los métodos:

FUNCTION /clean/read_alerts

Evita palabras poco específicas como "data", "info", "object"

Clean ABAP > Contenido > Nomenclatura > Esta sección

Omite palabras que generan ruido:

account  " instead of account_data
alert    " instead of alert_object

o reemplazalas con algo específico que realmente agregue valor

user_preferences          " instead of user_info
response_time_in_seconds  " instead of response_time_variable

Lee más en Capítulo 2: Nombres con sentido: Realizar distinciones con sentido de [Código Limpio por Robert C. Martin]

Elige una palabra por concepto

Clean ABAP > Contenido > Nomenclatura > Esta sección

METHODS read_this.
METHODS read_that.
METHODS read_those.

Elige un término para un concepto y apégate a él; no lo mezcles usando otros sinónimos. Los sinónimos harán al lector perder el tiempo buscando una diferencia que no está ahí.

" anti-pattern
METHODS read_this.
METHODS retrieve_that.
METHODS query_those.

Lee más en Capítulo 2: Nombres con sentido: Una palabra por concepto de [Código Limpio por Robert C. Martin]

Usa nombres de patrones solo si los estás usando

Clean ABAP > Contenido > Nomenclatura > Esta sección

No uses los nombres de patrones de diseño de software para clases e interfaces a menos que de verdad los estés usando. Por ejemplo, no llames a tu clase file_factory a menos que realmente implemente el patrón de diseño de fábrica. Los patrones más comunes son: singleton, factory, facade, composite, decorator, iterator, observer, y strategy.

Lee más en Capítulo 2: Nombres con sentido: Evitar la desinformación de [Código Limpio por Robert C. Martin]

Evita codificaciones, como notación Húngara y prefijos

Clean ABAP > Contenido > Nomenclatura > Esta sección

Te sugerimos que te deshagas de todas las codificaciones con prefijos.

METHOD add_two_numbers.
  result = a + b.
ENDMETHOD.

en lugar del innecesariamente largo

METHOD add_two_numbers.
  rv_result = iv_a + iv_b.
ENDMETHOD.

Evita codificaciones describe el razonamiento a detalle.

Lenguaje

Clean ABAP > Contenido > Esta sección

Considera el legacy

Clean ABAP > Contenido > Lenguaje > Esta sección

Si programas para versiones anteriores de ABAP, toma el consejo de esta guía con cuidado: Varias recomendaciones hacen uso de síntaxis relativamente nueva que podría no estar soportada en versiones anteriores de ABAP. Verifica que la guía que quieres seguir es viable en la versión más vieja que tienes que soportar. No descartes Código Limpio completamente - la gran mayoría de las reglas (ej. nomenclatura, comentarios) funcionarán en cualquier versión de ABAP.

Considera el rendimiento

Clean ABAP > Contenido > Lenguaje > Esta sección

Si programas componentes de alto rendimiento, toma el consejo de esta guía con cuidado: Algunos aspectos de Código Limpio pueden hacer las cosas más lentas (más llamadas a métodos) o consumir más memoria (más objetos). ABAP tiene algunas particularidades que pueden intensificar este detalle, por ejemplo compara los tipos de dato al llamar un método, por lo que al dividir un método largo en varios sub-métodos puede hacer el código más lento.

Sin embargo, recomendamos fuertemente no optimizar prematuramente, basándose en miedos irracionales. La gran mayoría de las reglas (ej. nomenclatura, comentarios) no tiene impacto negativo. Trata de construir las cosas de una manera limpia, orientada a objetos. Si algo es muy lento, haz una medición de rendimiento. Solo entonces deberías tomar una decisión, basada en hechos, para descartar reglas.

Algunos pensamientos adicionales, tomados en parte del Capítulo 2 de Refactoring por Martin Fowler:

En una aplicación típica la mayoría del tiempo de ejecución se concentra en una proporción muy pequeña del código. Tan poco como 10% del código puede consumir el 90% del tiempo de ejecución y especialmente en ABAP una proporción grande del tiempo de ejecución se suele concentrar en bases de datos.

Por lo tanto, no es el mejor uso de los recursos dedicar esfuerzo significativo en tratar de hacer todo el código súper eficiente todo el tiempo. No estamos sugiriendo que ignores el rendimiento, pero trata de enfocarte en código más limpio y estructurado durante el desarrollo inicial y usa el profiler de ABAP para identificar áreas críticas para optimizar.

De hecho, argumentamos que una estrategia como esta tiene un efecto neto positivo en el rendimiento ya que es un esfuerzo enfocado de optimización y debería ser más fácil detectar cuellos de botella, refactorizar y afinar código bien estructurado.

Prefiere orientado a objetos que programación procedural

Clean ABAP > Contenido > Lenguaje > Esta sección

Los programas orientados a objetos (clases, interfaces) están mejor segmentados y pueden ser refactorizados y probados más fácilmente que el código procedural (funciones, programas). Aunque hay situaciones donde debes usar objetos procedurales (una función para una RFC, un programa para una transacción), estos objetos deberían hcaer poco menos que llamar la clase correspondiente que contiene la funcionalidad:

FUNCTION check_business_partner [...].
  DATA(validator) = NEW /clean/biz_partner_validator( ).
  result = validator->validate( business_partners ).
ENDFUNCTION.

Grupos de funciones vs. Clases describe la diferencia a detalle.

Prefiere sentencias funcionales a procedurales

Clean ABAP > Contenido > Lenguaje > Esta sección

Son usualmente más cortas y son más naturales para los programadores modernos.

DATA(variable) = 'A'.
" MOVE 'A' TO variable.

DATA(uppercase) = to_upper( lowercase ).
" TRANSLATE lowercase TO UPPER CASE.

index += 1.         " >= NW 7.54
index = index + 1.  " < NW 7.54
" ADD 1 TO index.

DATA(object) = NEW /clean/my_class( ).
" CREATE OBJECT object TYPE /dirty/my_class.

result = VALUE #( FOR row IN input ( row-text ) ).
" LOOP AT input INTO DATA(row).
"  INSERT row-text INTO TABLE result.
" ENDLOOP.

DATA(line) = value_pairs[ name = 'A' ]. " entry must exist
DATA(line) = VALUE #( value_pairs[ name = 'A' ] OPTIONAL ). " entry can be missing
" READ TABLE value_pairs INTO DATA(line) WITH KEY name = 'A'.

DATA(exists) = xsdbool( line_exists( value_pairs[ name = 'A' ] ) ).
IF line_exists( value_pairs[ name = 'A' ] ).
" READ TABLE value_pairs TRANSPORTING NO FIELDS WITH KEY name = 'A'.
" DATA(exists) = xsdbool( sy-subrc = 0 ).

Varias de las reglas detalladas más adelante son reiteraciones específicas de este mismo punto.

Evita elementos obsoletos del lenguaje

Clean ABAP > Contenido > Lenguaje > Esta sección

Cuando actualices tu versión de ABAP, asegúrate de revisar elementos obsoletos del lenguaje y evita utilizarlos.

Por ejemplo, las variables de entorno se escapan con @, lo cual hace más claro qué es una variable del programa y qué es una columna en la base de datos,

SELECT *
  FROM spfli
  WHERE carrid = @carrid AND
        connid = @connid
  INTO TABLE @itab.

comparada con la versión obsoleta sin escapar

SELECT *
  FROM spfli
  WHERE carrid = carrid AND
        connid = connid
  INTO TABLE itab.

Nuevas alternativas tienden a mejorar la legibilidad del código y reducir conflictos de diseño con paradigmas modernos de programación, de manera que cambiar a ellas pueden automáticamente resultar en código más limpio.

Aunque continuan funcionando, elementos obsoletos pueden dejar de beneficiarse de optimizaciones en términos de velocidad de procesamiento y consumo de memoria.

Con elementos modernos del lenguaje, puedes entrenar más fácilmente a los ABAPers más jóvenes quienes no estarán familiarizados con las sentencias obsoletas ya que ya no se enseñan en los entrenamientos de SAP.

La documentación de SAP NetWeaver contiene una sección actualizada que enlista elementos obsoletos del lenguaje: NW 7.50, NW 7.51, NW 7.52, NW 7.53.

Usa patrones de diseño sabiamente

Clean ABAP > Contenido > Lenguaje > Esta sección

Donde sean apropiados y provean un beneficio tangible. No apliques patrones de diseño en todas partes solo por hacerlo.

Constantes

Clean ABAP > Contenido > Esta sección

Usa constantes en lugar de números mágicos

Clean ABAP > Contenido > Constantes > Esta sección

IF abap_type = cl_abap_typedescr=>typekind_date.

es más claro que

" anti-pattern
IF abap_type = 'D'.

Lee más en Capítulo 17: Síntomas y heurística: G25: Sustituir números mágicos por constantes con nombre de [Código Limpio por Robert C. Martin].

Prefiere clases de enumeración a interfaces de constantes

Clean ABAP > Contenido > Constantes > Esta sección

CLASS /clean/message_severity DEFINITION PUBLIC ABSTRACT FINAL.
  PUBLIC SECTION.
    CONSTANTS:
      warning TYPE symsgty VALUE 'W',
      error   TYPE symsgty VALUE 'E'.
ENDCLASS.

o

CLASS /clean/message_severity DEFINITION PUBLIC CREATE PRIVATE FINAL.
  PUBLIC SECTION.
    CLASS-DATA:
      warning TYPE REF TO /clean/message_severity READ-ONLY,
      error   TYPE REF TO /clean/message_severity READ-ONLY.
  " ...
ENDCLASS.

en lugar de mezclar conceptos no relacionados o llevar erróneamente a la gente a la conclusión de que las colecciones de constantes pueden ser "implementadas":

" anti-pattern
INTERFACE /dirty/common_constants.
  CONSTANTS:
    warning      TYPE symsgty VALUE 'W',
    transitional TYPE i       VALUE 1,
    error        TYPE symsgty VALUE 'E',
    persisted    TYPE i       VALUE 2.
ENDINTERFACE.

Enumeraciones describe patrones comunes de enumeración y discute sus ventajas y desventajas.

Lee más en Capítulo 17: Síntomas y heurística: J3: Constantes frente a enumeraciones de [Código Limpio por Robert C. Martin].

Si no usas clases de enumeración, agrupa tus constantes

Clean ABAP > Contenido > Constantes > Esta sección

Si juntas constantes de una manera desordenada, por ejemplo en una interfaz, agrúpalas:

CONSTANTS:
  BEGIN OF message_severity,
    warning TYPE symsgty VALUE 'W',
    error   TYPE symsgty VALUE 'E',
  END OF message_severity,
  BEGIN OF message_lifespan,
    transitional TYPE i VALUE 1,
    persisted    TYPE i VALUE 2,
  END OF message_lifespan.

Hace la relación más clara que:

" Anti-pattern
CONSTANTS:
  warning      TYPE symsgty VALUE 'W',
  transitional TYPE i       VALUE 1,
  error        TYPE symsgty VALUE 'E',
  persisted    TYPE i       VALUE 2,

El grupo también te permite acceder a ellas en grupo, por ejemplo para validación de entradas:

DO.
  ASSIGN COMPONENT sy-index OF STRUCTURE message_severity TO FIELD-SYMBOL(<constant>).
  IF sy-subrc IS INITIAL.
    IF input = <constant>.
      DATA(is_valid) = abap_true.
      RETURN.
    ENDIF.
  ELSE.
    RETURN.
  ENDIF.
ENDDO.

Lee más en Capítulo 17: Síntomas y heurística: G27: Estructura sobre convención de [Código Limpio por Robert C. Martin].

Variables

Clean ABAP > Contenido > Esta sección

Prefiere declaraciones in-line que al inicio

Clean ABAP > Contenido > Variables > Esta sección

Si sigues esta guía, tus métodos se volverán tan cortos (3-5 sentencias) que declarar variables in-line será más natural

METHOD do_something.
  DATA(name) = 'something'.
  DATA(reader) = /clean/reader=>get_instance_for( name ).
  result = reader->read_it( ).
ENDMETHOD.

que declarar variables con una sección separada de DATA al inicio del método

" anti-pattern
METHOD do_something.
  DATA:
    name   TYPE seoclsname,
    reader TYPE REF TO /dirty/reader.
  name = 'something'.
  reader = /dirty/reader=>get_instance_for( name ).
  result = reader->read_it( ).
ENDMETHOD.

Lee más en Capítulo 5: Formato vertical: Distancia Vertical: Declaraciones de variables de [Código Limpio por Robert C. Martin].

No declares variables en ramas opcionales

Clean ABAP > Contenido > Variables > Esta sección

" anti-pattern
IF has_entries = abap_true.
  DATA(value) = 1.
ELSE.
  value = 2.
ENDIF.

Esto funciona bien porque ABAP maneja declaraciones in-line como si estuvieran al inicio del método. Sin embargo, es extremadamente confuso para los lectores, especialmente si el método es largo y no detectas la declaración inmediatamente. En este caso, no uses in-line y coloca la declaración al inicio.

DATA value TYPE i.
IF has_entries = abap_true.
  value = 1.
ELSE.
  value = 2.
ENDIF.

Lee más en Capítulo 5: Formato vertical: Distancia Vertical: Declaraciones de variables de [Código Limpio por Robert C. Martin].

No encadenes declaraciones

Clean ABAP > Contenido > Variables > Esta sección

DATA name TYPE seoclsname.
DATA reader TYPE REF TO reader.

El encadenamiento sugiere que las variables definidas están relacionadas en un nivel lógico. Para usarlo consistentemente, tienes que asegurarte de que todas las variables encadenadas pertenecen juntas lógicamente e introducir grupos encadenados adicionales para agregar variables. Aunque esto es posible, el esfuerzo rara vez vale la pena.

El encadenamiento complica innecesariamente reformatear y refactorizar el código, ya que cada línea se ve diferente y cambiarlas requiere lidiar con comas, puntos, dos puntos, lo cual no vale la pena.

" anti-pattern
DATA:
  name   TYPE seoclsname,
  reader TYPE REF TO reader.

También lee No alinees los TYPE
Si el encadenamiento de la declaración de datos es usada, entonces usa una cadena por cada grupo de variables que pertenecen juntas lógicamente.

Prefiere REF TO a FIELD-SYMBOL

Clean ABAP > Contenido > Variables > Esta sección

Esta sección está siendo discutida. FIELD-SYMBOL parece ser considerablemente más rápida al iterar sobre tablas internas, tanto que la recomendación de usar REF TO para estos casos puede empeorar el rendimiento.

LOOP AT components REFERENCE INTO DATA(component).

en lugar del equivalente

" anti-pattern
LOOP AT components ASSIGNING FIELD-SYMBOL(<component>).

excepto cuando se necesiten los field-symbols

ASSIGN generic->* TO FIELD-SYMBOL(<generic>).
ASSIGN COMPONENT name OF STRUCTURE structure TO FIELD-SYMBOL(<component>).
ASSIGN (class_name)=>(static_member) TO FIELD-SYMBOL(<member>).

Las revisiones de código demuestran que la gente tiende a escoger entre las dos arbitrariamente, "solo porque sí", "porque siempre hacemos LOOPs de esa manera", o "sin una razón en particular". Las decisiones arbitrarias hacen que el lector pierda el tiempo en la pregunta sin sentido de por qué se usó uno en lugar del otro, por lo tanto se debería reemplazar con decisiones precisas y bien fundamentadas. Nuestra recomendación está basada en el siguiente razonamiento:

  • Los field-symbols pueden hacer cosas que las referencias no pueden, como acceder dinámicamente a los componentes de una estructura. De la misma manera, las referencias pueden hacer cosas que los field-symbols no, como construir dinámicamente una estructura de datos con tipos. En resumen, elegir una sola para todas las situaciones no es posible.

  • En ABAP orientado a objetos, las referencias se usan por todas partes y no se pueden evitar, ya que cualquier objeto es una REF TO <class-name>. En contraste, los field-symbols son solamente requeridos estrictamente en situaciones especiales al usar programación dinámica. Las referencias por lo tanto tienen una preferencia natural en cualquier programa orientado a objetos.

  • Los field-symbols son más cortos que las referencias, pero el ahorro resultante en memoria es tan pequeño que puede ser ignorado sin problema. De la misma manera, la velocidad no es un problema. Como consecuencia, no hay razón relacionada con el rendimiento para preferir uno sobre el otro.

Lee más en el artículo Accessing Data Objects Dynamically in the ABAP Programming Guidelines.

Tablas

Clean ABAP > Contenido > Esta sección

Usa el tipo correcto de tabla

Clean ABAP > Contenido > Tablas > Esta sección

  • Típicamente se usan tablas del tipo HASHED para tablas grandes que son llenadas en un solo paso, nunca se modifican y son leídas seguido por su llave. Su sobrecoste en memoria y procesamiento hace a las tablas hash únicamente valiosas cuando se tienen cantidades grandes de datos y muchos accesos de lectura. Cada cambio al contenido de la tabla requiere costosos recálculos del hash, así que no uses este tipo para tablas que son modificadas muy frecuentemente.

  • Típicamente se usan tablas del tipo SORTED para tablas grandes que necesitan estar ordenadas en todo momento, que son llenadas poco a poco o que necesitan ser modificadas y leídas por una o más claves parciales o completas o procesadas en cierto orden. Agregar, cambiar o quitar contenido require encontrar el punto de inserción adecuado, pero no requiere reajustar los índices de la tabla completa. Las tablas ordenadas demustran su valor únicamente para cantidades grandes de accesos de lectura.

  • Usa tablas del tipo STANDARD para tablas pequeñas, donde el indexado genera más coste que beneficio, y arreglos, donde no te interesa el orden de las filas o quieres procesarlas exactamente en el orden que se agregaron a la tabla. Además, si se requieren diferentes tipos de acceso a la tabla (por ejemplo, acceso por índice y acceso ordenado via SORT y BINARY SEARCH)

Ésta es una guía a grandes rasgos. Encuentra más detalles en Selection of Table Category in the ABAP Language Help.

Evita usar DEFAULT KEY

Clean ABAP > Contenido > Tablas > Esta sección

" anti-pattern
DATA itab TYPE STANDARD TABLE OF row_type WITH DEFAULT KEY.

Las claves por defecto comúnmente son solo agregadas a las sentencias nuevas funcionales para que funcionen. Las claves en sí mismas son usualmente superfluas y desperdician recursos sin ningún motivo. Pueden incluso llevar a errores difíciles de detectar porque ignoran los tipos de datos numéricos. Las sentencias SORT y DELETE ADJACENT sin una lista explícita de campos va a utilizar la clave primaria de la tabla interna, que en el caso de DEFAULT KEY puede provocar resultados inesperados cuando tengas campos numéricos como componente de la clave, en particular en combinación con READ TABLE ... BINARY, etc.

Especifica los componentes de la llave explícitamente

DATA itab2 TYPE STANDARD TABLE OF row_type WITH NON-UNIQUE KEY comp1 comp2.

o recurre a usar EMPTY KEY si no necesitas la clave para nada.

DATA itab1 TYPE STANDARD TABLE OF row_type WITH EMPTY KEY.

Para más detalles, lee Horst Keller's blog on Internal Tables with Empty Key

Precaución: SORT en tablas internas con EMPTY KEY (sin campos de ordenamiento explícitos) no va a ordenar nada, pero aparecerán advertencias de síntaxis en caso de que se pueda determinar estáticamente que la clave está vacía.

Prefiere INSERT INTO TABLE a APPEND TO

Clean ABAP > Contenido > Tablas > Esta sección

INSERT VALUE #( ... ) INTO TABLE itab.

INSERT INTO TABLE funciona con todos los tipos de tabla y de clave, por lo tanto haciendo más fácil refactorizar el tipo de tabla y definiciones clave si los requerimientos de rendimiento cambian.

Utiliza APPEND TO únicamente si usas una tabla STANDARD en modo de arreglo, si quieres dejar claro que el registro añadido será el último.

Prefiere LINE_EXISTS a READ TABLE o LOOP AT

Clean ABAP > Contenido > Tablas > Esta sección

IF line_exists( my_table[ key = 'A' ] ).

expresa la intención más claro y corto que

" anti-pattern
READ TABLE my_table TRANSPORTING NO FIELDS WITH KEY key = 'A'.
IF sy-subrc = 0.

o incluso

" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.
  line_exists = abap_true.
  EXIT.
ENDLOOP.

Prefiere READ TABLE a LOOP AT

Clean ABAP > Contenido > Tablas > Esta sección

READ TABLE my_table REFERENCE INTO DATA(line) WITH KEY key = 'A'.

expresa la intención más claro y corto que

" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.
  EXIT.
ENDLOOP.

o incluso

" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line).
  IF line->key = 'A'.
    EXIT.
  ENDIF.
ENDLOOP.

Prefiere LOOP AT WHERE a IF anidado

Clean ABAP > Contenido > Tablas > Esta sección

LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.

expresa la intención más claro y corto que

LOOP AT my_table REFERENCE INTO DATA(line).
  IF line->key = 'A'.
    EXIT.
  ENDIF.
ENDLOOP.

Evita lecturas innecesarias a tablas

Clean ABAP > Contenido > Tablas > Esta sección

En caso de que esperes que un registro esté ahí, lee una vez y reacciona a la excepción,

TRY.
    DATA(row) = my_table[ key = input ].
  CATCH cx_sy_itab_line_not_found.
    RAISE EXCEPTION NEW /clean/my_data_not_found( ).
ENDTRY.

en lugar de contaminar y alentar el flujo de control principal con una lectura doble.

" anti-pattern
IF NOT line_exists( my_table[ key = input ] ).
  RAISE EXCEPTION NEW /clean/my_data_not_found( ).
ENDIF.
DATA(row) = my_table[ key = input ].

Además de ser una mejora de rendimiento, esta es una variante especial de la sección más general Enfócate en el happy path o en manejo de errores, no en ambos.

Strings

Clean ABAP > Contenido > Esta sección

Usa ` para definir literales

Clean ABAP > Contenido > Strings > Esta sección

CONSTANTS some_constant TYPE string VALUE `ABC`.
DATA(some_string) = `ABC`.  " --> TYPE string

Evita usar ', ya que hace una conversión de tipo superflua y confunde al lector sobre si está lidiando con un CHAR o un STRING:

" anti-pattern
DATA some_string TYPE string.
some_string = 'ABC'.

| está generalmente bien, pero no puede ser usada para CONSTANTS y agrega coste innecesario cuando se especifica un valor fijo:

" anti-pattern
DATA(some_string) = |ABC|.

Usa | para construir textos

Clean ABAP > Contenido > Strings > Esta sección

DATA(message) = |Received HTTP code { status_code } with message { text }|.

Las plantillas o templates de string resaltan mejor qué es un literal y qué es una variable, especialmente si colocas múltiples variables en un texto.

" anti-pattern
DATA(message) = `Received an unexpected HTTP ` && status_code && ` with message ` && text.

Booleanos

Clean ABAP > Contenido > Esta sección

Usa los booleanos sabiamente

Clean ABAP > Contenido > Booleanos > Esta sección

Frecuentemente encontramos casos donde los booleanos naturalmente parecen ser la opción

" anti-pattern
is_archived = abap_true.

Hasta que un cambio de punto de vista sugiere que debimos haber elegido una enumeración

archiving_status = /clean/archivation_status=>archiving_in_process.

Generalmente, los booleanos son una mala elección para distinguir tipos de cosas porque casi siempre encontrarás casos que no son exclusivamente uno u otro

assert_true( xsdbool( document->is_archived( ) = abap_true AND
                      document->is_partially_archived( ) = abap_true ) ).

Separa los métodos, en lugar de recibir parámetros booleanos de entrada explica más a detalle por qué siempre debes cuestionarte usar parámetros booleanos.

Lee más en 1

Usa ABAP_BOOL para booleanos

Clean ABAP > Contenido > Booleanos > Esta sección

DATA has_entries TYPE abap_bool.

No uses el tipo genérico char1. Aunque es técnicamente compatible, esconde el hecho de que estamos lidiando con una variable booleana.

También evita otros tipos de booleanos ya que a menudo tienen efectos secundarios, por ejemplo el tipo boolean usa un tercer valor llamado "undefined" que resulta en sutiles errores de programación.

En algunos casos, puedes necesitar un elemento de diccionario de datos, por ejemplo para campos de DynPro. abap_bool no puede ser usado en este caso porque está definido en el type pool abap, no en el diccionario de datos.

En este caso, utiliza boole_d o xfeld. Crea tu propio elemento de datos si necesitas una descripción personalizada.

Puede que ABAP sea el único lenguaje de programación que no tiene un tipo de datos booleano universal. Sin embargo, tener uno es imperativo. Esta recomendación está basada en las ABAP Programming Guidelines.

Usa ABAP_TRUE y ABAP_FALSE para hacer comparaciones

Clean ABAP > Contenido > Booleanos > Esta sección

has_entries = abap_true.
IF has_entries = abap_false.

No uses los equivalentes en caracter 'X' y ' ' o space; hacen que sea más difícil identificar que es una expresión booleana:

" anti-pattern
has_entries = 'X'.
IF has_entries = space.

Evita comparaciones con INITIAL - fuerza al lector a recordar que el default de abap_bool es abap_false:

" anti-pattern
IF has_entries IS NOT INITIAL.

Puede que ABAP sea el único lenguaje de programación que no constantes integradas para verdadero y falso. Sin embargo, tenerlas es imperativo. Esta recomendación está basada en las ABAP Programming Guidelines.

Usa XSDBOOL para asignar variables booleanas

Clean ABAP > Contenido > Booleanos > Esta sección

DATA(has_entries) = xsdbool( line IS NOT INITIAL ).

El equivalente IF-THEN-ELSE es mucho más largo sin sentido:

" anti-pattern
IF line IS INITIAL.
  has_entries = abap_false.
ELSE.
  has_entries = abap_true.
ENDIF.

xsdbool es la mejor manera para nuestro propósito, ya que directamente genera un char1, lo que funciona perfecto con nuestro tipo abap_bool. Las funciones equivalentes boolc y boolx producen tipos diferentes y causan una conversión de tipo implícita innecesaria.

Estamos de acuerdo que el nombre xsdbool es desafortunado; después de todo, no estamos interesados en el "XML Schema Definition" que sugiere el prefijo "xsd".

Una posible alternativa a xsdbool es la forma ternaria de COND. Su sintaxis es intuitiva, pero un poco más larga porque repite innecesariamente el segmento THEN abap_true, y requiere conocimiento del valor implícito por default abap_false - razón por la cual lo sugerimos únicamente como una solución secundaria.

DATA(has_entries) = COND abap_bool( WHEN line IS NOT INITIAL THEN abap_true ).

Condiciones

Clean ABAP > Contenido > Esta sección

Intenta hacer tus condiciones positivas

Clean ABAP > Contenido > Condiciones > Esta sección

IF has_entries = abap_true.

Para fines de comparación, observa lo difícil que se vuelve entender la misma sentencia invirtiéndola:

" anti-pattern
IF has_no_entries = abap_false.

El "intenta" en el título de la sección se refiere a que no debes forzar este consejo al punto de que termines con algo como ramas IF vacías:

" anti-pattern
IF has_entries = abap_true.
ELSE.
  " only do something in the ELSE block, IF remains empty
ENDIF.

Lee más en Capítulo 17: Síntomas y heurística: G29: Evitar condicionales negativas de [Código Limpio por Robert C. Martin].

Prefiere IS NOT sobre NOT IS

Clean ABAP > Contenido > Condiciones > Esta sección

IF variable IS NOT INITIAL.
IF variable NP 'TODO*'.
IF variable <> 42.

La negación es lógicamente equivalente pero requiere un "cambio mental de perspectiva" que hace que sea más difícil de entender.

" anti-pattern
IF NOT variable IS INITIAL.
IF NOT variable CP 'TODO*'.
IF NOT variable = 42.

Una variante más específica de Intenta hacer tus condiciones positivas También descrito en la sección Alternative Language Constructs de las ABAP programming guidelines.

Considera descomponer condiciones complejas

Clean ABAP > Contenido > Condiciones > Esta sección

Las condiciones pueden volverse más fáciles al descomponerlas en sus componentes elementales que las conforman:

DATA(example_provided) = xsdbool( example_a IS NOT INITIAL OR
                                  example_b IS NOT INITIAL ).

DATA(one_example_fits) = xsdbool( applies( example_a ) = abap_true OR
                                  applies( example_b ) = abap_true OR
                                  fits( example_b ) = abap_true ).

IF example_provided = abap_true AND
   one_example_fits = abap_true.

en lugar de dejar todo en su lugar:

" anti-pattern
IF ( example_a IS NOT INITIAL OR
     example_b IS NOT INITIAL ) AND
   ( applies( example_a ) = abap_true OR
     applies( example_b ) = abap_true OR
     fits( example_b ) = abap_true ).

Usa los quick-fixes de ABAP Development Tools para rápidamente extraer condiciones y crear variables como las mostradas.

Considera extraer condiciones complejas

Clean ABAP > Contenido > Condiciones > Esta sección

Casi siempre es una buena idea extraer condiciones complejas a su propio método:

IF is_provided( example ).

METHOD is_provided.
  DATA(is_filled) = xsdbool( example IS NOT INITIAL ).
  DATA(is_working) = xsdbool( applies( example ) = abap_true OR
                              fits( example ) = abap_true ).
  result = xsdbool( is_filled = abap_true AND
                    is_working = abap_true ).
ENDMETHOD.

Ifs

Clean ABAP > Contenido > Esta sección

No dejes ramas IF vacías

Clean ABAP > Contenido > Ifs > Esta sección

IF has_entries = abap_false.
  " do some magic
ENDIF.

es más corto y claro que

" anti-pattern
IF has_entries = abap_true.
ELSE.
  " do some magic
ENDIF.

Prefiere CASE a ELSE IF para múltiples condiciones

Clean ABAP > Contenido > Ifs > Esta sección

CASE type.
  WHEN type-some_type.
    " ...
  WHEN type-some_other_type.
    " ...
  WHEN OTHERS.
    RAISE EXCEPTION NEW /clean/unknown_type_failure( ).
ENDCASE.

CASE hace más fácil ver un conjunto de alternativas que se excluyen mutuamente. Puede ser más rápido que una serie de IFs porque puede traducirse a una instrucción de microprocesador diferente, en lugar de una serie de condiciones evaluadas subsecuentemente. Puedes introducir casos nuevos rápidamente, sin tener que repetir la variable una y otra vez. La sentencia incluso previene algunos errores que pueden ocurrir al anidar incorrectamente los IF-ELSEIFs.

" anti-pattern
IF type = type-some_type.
  " ...
ELSEIF type = type-some_other_type.
  " ...
ELSE.
  RAISE EXCEPTION NEW /dirty/unknown_type_failure( ).
ENDIF.

Mantén la profundidad del anidamiento baja

Clean ABAP > Contenido > Ifs > Esta sección

" anti-pattern
IF <this>.
  IF <that>.
  ENDIF.
ELSE.
  IF <other>.
  ELSE.
    IF <something>.
    ENDIF.
  ENDIF.
ENDIF.

Los IFs anidados se vuelven difíciles de entender muy rápidamente y requiren un número de casos de prueba exponencialmente mayor para lograr cobertura completa del código.

Los árboles de decisión típicamente se pueden deshacer haciendo sub-métodos e introduciendo variables auxiliares booleanas.

Otros casos pueden ser simplificados uniendo IFs, como

IF <this> AND <that>.

en lugar del innecesariamente anidado

" anti-pattern
IF <this>.
  IF <that>.

Expresiones regulares

Clean ABAP > Contenido > Esta sección

Prefiere métodos simples a expresiones regulares

Clean ABAP > Contenido > Expresiones regulares > Esta sección

IF input IS NOT INITIAL.
" IF matches( val = input  regex = '.+' ).

WHILE contains( val = input  sub = 'abc' ).
" WHILE contains( val = input  regex = 'abc' ).

Las expresiones regulares se vuelven difíciles de entender muy rápidamente. Los casos simples son usualmente más fáciles sin ellas.

Las expresiones regulares también usualmente consumen más memoria y tiempo de procesamiento, porque necesitan ser analizadas gramaticalmente en un árbol de expresiones y compiladas en tiempo de ejecución en un matcher ejecutable. Las soluciones simples pueden estar mejor servidas con un LOOP y una variable temporal.

Prefiere revisiones basis a expresiones regulares

Clean ABAP > Contenido > Expresiones regulares > Esta sección

CALL FUNCTION 'SEO_CLIF_CHECK_NAME'
  EXPORTING
    cls_name = class_name
  EXCEPTIONS
    ...

en lugar de reinventar la rueda

" anti-pattern
DATA(is_valid) = matches( val     = class_name
                          pattern = '[A-Z][A-Z0-9_]{0,29}' ).

Parece haber una tendencia natural al principio de Don't Repeat Yourself (DRY) o No Te Repitas cuando hay expresiones regulares en uso, compara con la sección Capítulo 17: Síntomas y heurística: General: G5: Duplicación en [Código Limpio por Robert C. Martin].

Considera construir expresiones regulares complejas

Clean ABAP > Contenido > Expresiones regulares > Esta sección

CONSTANTS class_name TYPE string VALUE `CL\_.*`.
CONSTANTS interface_name TYPE string VALUE `IF\_.*`.
DATA(object_name) = |{ class_name }\|{ interface_name }|.

Algunas expresiones regulares complejas se vuelven más sencillas cuando demuestras al lector cómo están construidas a partir de sus piezas elementales.

Clases

Clean ABAP > Contenido > Esta sección

Classes: Object orientation

Clean ABAP > Contenido > Clases > Esta sección

Prefiere objetos a clases estáticas

Clean ABAP > Contenido > Clases > Clases: Orientación a objetos > Esta sección

Las clases estáticas impiden todas las ventajas que se obtienen utilizando programación orientada a objetos. Especialmente vuelven cerca de imposible reemplazar dependencias productivas con dobles de prueba en pruebas unitarias automatizadas.

Si piensas si debes hacer una clase o método estática, la respuesta casi siempre será: no.

Una excepción típica a esta regla son las clases de utilerías. Sus métodos hacen más fácil interactuar con ciertos tipos de ABAP. No solo son completamente stateless, pero tan básicas que parecen sentencias ABAP o funciones incluidas con ell lenguaje. El factor discriminante es que sus consumidores las atan a su código tan fuertemente que realmente no quieren hacerles un mock en sus pruebas unitarias.

CLASS /clean/string_utils DEFINITION [...].
  CLASS-METHODS trim
   IMPORTING
     string        TYPE string
   RETURNING
     VALUE(result) TYPE string.
ENDCLASS.

METHOD retrieve.
  DATA(trimmed_name) = /clean/string_utils=>trim( name ).
  result = read( trimmed_name ).
ENDMETHOD.

Prefiere composición a herencia

Clean ABAP > Contenido > Clases > Clases: Orientación a objetos > Esta sección

Evita construir jerarquías de clases con herencia. En su lugar, prefiere la composición.

La herencia limpia es difícil de diseñar porque necesitas respetar reglas como el Principio de sustitución de Liskov.

También es difícil de entender porque las personas necesitan analizar y digerir los principios guía detrás de la jerarquía. La herencia reduce el reuso, dado que los métodos tienden a solo estar disponibles para las sub-clases. También complica la refactorización, porque mover o cambiar números tiende a requerir cambios a todo el árbol de la jerarquía.

La composición significa que diseñas objetos pequeños e independientes, donde cada uno sirve un propósito específico. Estos objetos pueden ser recombinados en objetos más complejos mediante simple delegación y patrones de facade. La composición puede producir más clases, pero además de eso no tiene otras desventajas.

No permitas que esta regla te desanime de usar herencia cuando es correcto hacerlo. Hay buenas applicaciones para herencia, por ejemplo el Patrón de diseño Composite. Solo pregúntate críticamente si la herencia en tu caso realmente proveerá más beneficios que desventajas. Si tienes duda, la composición es generalmente la opción más segura.

Interfaces vs. clases abstractas compara algunos detalles.

No mezcles lógica stateful y stateless en la misma clase

Clean ABAP > Contenido > Clases > Clases: Orientación a objetos > [Esta sección] > (#no-mezcles-lógica-stateful-y-stateless-en-la-misma-clase)

No mezcles los paradigmas de programación stateless y stateful en la misma clase.

En programación stateless, los métodos reciben una entrada y producen una salida sin ningún efecto secundario, resultando en métodos que producen el mismo resultado sin importar en qué orden fueron llamados.

CLASS /clean/xml_converter DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS convert
      IMPORTING
        file_contenido  TYPE xstring
      RETURNING
        VALUE(result) TYPE /clean/some_inbound_message.
ENDCLASS.

CLASS /clean/xml_converter IMPLEMENTATION.
  METHOD convert.
    cl_proxy_xml_transform=>xml_xstring_to_abap(
      EXPORTING
        xml       = file_contenido
        ext_xml   = abap_true
        svar_name = 'ROOT_NODE'
      IMPORTING
        abap_data = result ).
   ENDMETHOD.
ENDCLASS.

En programación stateful, manipulamos el estado interno del objeto a través de sus métodos, lo que significa que está llena de efectos secundarios.

CLASS /clean/log DEFINITION PUBLIC CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS add_message IMPORTING message TYPE /clean/message.
  PRIVATE SECTION.
    DATA messages TYPE /clean/message_table.
ENDCLASS.

CLASS /clean/log IMPLEMENTATION.
  METHOD add_message.
    INSERT message INTO TABLE messages.
  ENDMETHOD.
ENDCLASS.

Ambos paradigmas están bien y cada uno tiene sus usos. Sin embargo, mezclarlos en el mismo objeto produce código que es difícil de entender y seguro que fallará con errores acarreados ocultos y problemas de sincronicidad. No lo haga, compa.

Alcance

Clean ABAP > Contenido > Clases > Esta sección

Global por default, solo local cuando sea apropiado

Clean ABAP > Contenido > Clases > Alcance > Esta sección

Usa clases globales por default. Usa clases locales solo cuando sea apropiado.

Las clases globales son las que son visibles en el diccionario de datos. Las clases locales viven dentro de un include de otro objeto de desarrollo y solo son visibles a ese objeto.

Las clases locales funcionan para

  • estructuras de datos privadas muy específicas, por ejemplo un iterador de los datos de la clase global, que nunca será usado en otra clase más que esta,

  • extraer un algoritmo privado complejo, por ejemplo para desenredar esa estrategia de ordenamiento y agregación que usa múltiples métodos del resto del código de tu clase,

  • para permitir hacer mock a ciertos aspectos de la clase global, por ejemplo extrayendo todos los accesos de base de datos a una clase local separada para que puedan ser reemplazados con un doble de prueba en las pruebas unitarias.

Las clases locales evitan el reuso porque no pueden ser usadas en ninguna otra parte. Aunque son fáciles de extraer, la gente fallará incluso en encontrarlas, llevando a indeseable código duplicado. Hacer debugging, navegar y orientarse en includes con clases locales extensas es tedioso y molesto.

Ya que ABAP bloquea los objetos a nivel de include, la gente no podrá trabajar en diferentes partes del include de manera simultánea (lo cual sería posible si fueran clases globales separadas).

Reconsidera tu uso de clases locales si

  • tu include local tiene decenas de clases y miles de líneas de código,
  • piensas de las clases globales como "paquetes" que almacenan otras clases,
  • tus clases globales terminan siendo cascarones vacíos
  • encuentras código duplicado a través de includes locales separados,
  • tus desarrolladores comienzan a bloquearse unos a los otros y dejan de poder trabajar en paralelo
  • tus estimaciones de backlog están por los cielos porque tus equipos no logran entender los sub-árboles locales de los otros miembros.

Marcar como FINAL si no fue diseñada para herencia

Clean ABAP > Contenido > Clases > Alcance > Esta sección

Marca las clases que no están diseñadas explícitamente para herencia como FINAL.

Al diseñar cooperación entre clases, tu primera opción debería ser composición, no herencia. Habilitar la herencia no es algo que se debe hacer a la ligera, ya que requiere que pienses en temas como PROTECTED vs. PRIVATE y el Principio de sustitución de Liskov, lo que congela muchos de los componentes internos. Si no consideraste estas cosas en tu diseño de clase, deberías evitar que alguien herede accidentalmente haciendo tu clase FINAL.

Existen algunos ejemplos buenos de uso de herencia, por ejemplo el patrón de diseño composite. Las BAdI también pueden volverse más útiles permitiendo las sub-clases, habilitando al cliente para reutilizar la mayoría del código original. Sin embargo, observa que todos estos casos tienen la herencia incluida desde su diseño.

Clases no limpias que no implementan interfaces deberían de dejarse como no FINAL para permitir a los consumidores hacer un mock de ellas en sus pruebas unitarias.

Miembros PRIVATE por default, PROTECTED solo si es necesario

Clean ABAP > Contenido > Clases > Alcance > Esta sección

Haz atributos, métodos y otros miembros de tu clase PRIVATE por default.

Solo hazlos PROTECTED si quieres que las sub-clases puedan re-definirlos.

Los componentes internos de una clase solo se deben hacer disponibles a otros cuando sea estrictamente necesario. Esto incluye no solamente consumidores externos, sino también las sub-clases. Permitir que información de más esté disponible puede provocar errores sútiles debido a re-definiciones inesperadas y complican la refactorización, debido a que los consumidores externos congelan los miembros que aún deberían estar líquidos.

Considera usar inmutables en lugar de getters

Clean ABAP > Contenido > Clases > Alcance > Esta sección

Un inmutable es un objeto que nunca cambia después de su construcción. Para este tipo de objeto considera usar atributos públicos y READ-ONLY, en lugar de métodos getter.

CLASS /clean/some_data_container DEFINITION.
  PUBLIC SECTION.
    METHODS constructor
      IMPORTING
        a TYPE i
        b TYPE c
        c TYPE d.
    DATA a TYPE i READ-ONLY.
    DATA b TYPE c READ-ONLY.
    DATA c TYPE d READ-ONLY.
ENDCLASS.

en lugar de

CLASS /dirty/some_data_container DEFINITION.
  PUBLIC SECTION.
    METHODS get_a ...
    METHODS get_b ...
    METHODS get_c ...
  PRIVATE SECTION.
    DATA a TYPE i.
    DATA b TYPE c.
    DATA c TYPE d.
ENDCLASS.

Precaución: Para los objetos que sí tienen valores que cambian, no usar atributos públicos READ-ONLY. De otra manera estos atributos se tienen que mantener actualizados, independientemente de si su valor se requiere por otro código o no.

Utiliza READ-ONLY con mesura

Clean ABAP > Contenido > Clases > Alcance > Esta sección

Varios lenguajes de programación modernos, especialmente Java, recomiendan hacer los miembros de la clase READ-ONLY donde sea apropiado para evitar efectos secundarios accidentales.

Mientras que ABAP odrece la adición READ-ONLY para declaraciones de datos, recomendamos usarla con mesura.

La adición solamente está disponible en la PUBLIC SECTION, reduciendo su uso drásticaamente. No puedes agregarla a la PROTECTED SECTION o PRIVATE SECTION, ni a las variables locales en un método.

Además, funciona ligeramente diferente de lo que la gente podría esperar de otros lenguajes de programación: Los datos con la adición READ-ONLY pueden ser aún modificados desde cualquier método de la clase, sus FRIENDS y sus sub-classes. Esto contradice el comportamiento más pervasivo de escribir-una-vez-nunca-modificar que podemos encontrar en otros lenguajes. Esta diferencia puede llevar a malas sorpresas.

Para evitar malentendidos: Proteger variables contra modificación accidental es una buena práctica. Recomendaríamos aplicarlo a ABAP también si hubiera una sentencia apropiada.

Constructores

Clean ABAP > Contenido > Clases > Esta sección

Prefiere NEW a CREATE OBJECT

Clean ABAP > Contenido > Clases > Constructores > Esta sección

DATA object TYPE REF TO /clean/some_number_range.
object = NEW #( '/CLEAN/CXTGEN' )
...
DATA(object) = NEW /clean/some_number_range( '/CLEAN/CXTGEN' ).
...
DATA(object) = CAST /clean/number_range( NEW /clean/some_number_range( '/CLEAN/CXTGEN' ) ).

en lugar del innecesariamente largo

" anti-pattern
DATA object TYPE REF TO /dirty/some_number_range.
CREATE OBJECT object
  EXPORTING
    number_range = '/DIRTY/CXTGEN'.

a excepción de cuando requieras tipos dinámicos, por supuesto

CREATE OBJECT number_range TYPE (dynamic_type)
  EXPORTING
    number_range = '/CLEAN/CXTGEN'.

Si tu clase global es CREATE PRIVATE, deja el constructor público

Clean ABAP > Contenido > Clases > Constructores > Esta sección

CLASS /clean/some_api DEFINITION PUBLIC FINAL CREATE PRIVATE.
  PUBLIC SECTION.
    METHODS constructor.

Estamos de acuerdo que esto se contradice a sí mismo.

Sin embargo, de acuerdo al artículo Instance Constructor of the ABAP Help, especificar el CONSTRUCTOR en la PUBLIC SECTION es requerido para garantizar correcta compilación y validación de sintaxis.

Esto aplica solamente a clases globales. En clases locales, haz el constructor privado, como debe ser.

Prefiere múltiples métodos de construcción estáticos a parámetros opcionales

Clean ABAP > Contenido > Clases > Constructores > Esta sección

CLASS-METHODS describe_by_data IMPORTING data TYPE any [...]
CLASS-METHODS describe_by_name IMPORTING name TYPE any [...]
CLASS-METHODS describe_by_object_ref IMPORTING object_ref TYPE REF TO object [...]
CLASS-METHODS describe_by_data_ref IMPORTING data_ref TYPE REF TO data [...]

ABAP no soporta sobrecarga. Usa variaciones de nombres y no parámetros opcionales para lograr la semántica requerida.

" anti-pattern
METHODS constructor
  IMPORTING
    data       TYPE any OPTIONAL
    name       TYPE any OPTIONAL
    object_ref TYPE REF TO object OPTIONAL
    data_ref   TYPE REF TO data OPTIONAL
  [...]

La sección general Separa métodos en lugar de agregar parámetros OPTIONAL explica el razonamiento detrás de esto.

Considera resolver construcciones complejas en una creación en varios pasos con el Patrón de diseo Builder.

Usa nombres descriptivos para múltiples métodos de construcción

Clean ABAP > Contenido > Clases > Constructores > Esta sección

Palabras adecuadas para comenzar métodos de creación son new_, create_, y construct_. La gente intuitivamente lo conectan a la construcción de objetos. También se prestan bien a frases con verbos como new_from_template, create_as_copy, o create_by_name.

CLASS-METHODS new_describe_by_data IMPORTING p_data TYPE any [...]
CLASS-METHODS new_describe_by_name IMPORTING p_name TYPE any [...]
CLASS-METHODS new_describe_by_object_ref IMPORTING p_object_ref TYPE REF TO object [...]
CLASS-METHODS new_describe_by_data_ref IMPORTING p_data_ref TYPE REF TO data [...]

en lugar de algo sin sentido como

" anti-pattern
CLASS-METHODS create_1 IMPORTING p_data TYPE any [...]
CLASS-METHODS create_2 IMPORTING p_name TYPE any [...]
CLASS-METHODS create_3 IMPORTING p_object_ref TYPE REF TO object [...]
CLASS-METHODS create_4 IMPORTING p_data_ref TYPE REF TO data [...]

Usa singletons únicamente cuando múltiples instancias no hacen sentido

Clean ABAP > Contenido > Clases > Constructores > Esta sección

METHOD new.
  IF singleton IS NOT BOUND.
    singleton = NEW /clean/my_class( ).
  ENDIF.
  result = singleton.
ENDMETHOD.

Aplica el patrón de diseño de Singleton cuando tu diseño orientado a objetos indica que tener una segunda instancia de algo no hace sentido. Úsalo para asegurar que cada consumidor que está trabajando con la misma clase tiene un objeto con el mismo estado y los mismos datos.

No uses el patrón Singleton por hábito o porque una regla de rendimiento te indica que lo hagas. Es el patrón de diseño más sobre-utilizado y mal aplicado y produce efectos secundarios no esperados y complica innecesariamente las pruebas. Si no hay razones de diseño para un objeto unitario, déjale esa decisión al consumidor - él puede llegar a ese mismo resultado por fuera del constructor, por ejemplo con un patrón de diseño de fábrica.

Métodos

Clean ABAP > Contenido > Esta sección

Estas reglas aplican a métodos en clases y módulos de funciones.

Llamadas

Clean ABAP > Contenido > Métodos > Esta sección

Prefiere llamadas funcionales a procedurales

Clean ABAP > Contenido > Métodos > Calls > Esta sección

modify->update( node           = /clean/my_bo_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

en lugar del innecesariamente largo

" anti-pattern
CALL METHOD modify->update
  EXPORTING
    node           = /dirty/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields.

Si la programación dinámica prohibe llamadas funcionales, recurre al estilo procedural

CALL METHOD modify->(method_name)
  EXPORTING
    node           = /clean/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields.

Varias de las reglas detalladas abajo son solo variaciones específicas de este consejo.

Omite el uso de RECEIVING

Clean ABAP > Contenido > Métodos > Calls > Esta sección

DATA(sum) = aggregate_values( values ).

en lugar del innecesariamente largo

" anti-pattern
aggregate_values(
  EXPORTING
    values = values
  RECEIVING
    result = DATA(sum) ).

Omite la palabra clave opcional EXPORTING

Clean ABAP > Contenido > Métodos > Calls > Esta sección

modify->update( node           = /clean/my_bo_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

en lugar del innecesariamente largo

" anti-pattern
modify->update(
  EXPORTING
    node           = /dirty/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields ).

Omite el nombre del parámetro en llamadas de un solo parámetro

Clean ABAP > Contenido > Métodos > Calls > Esta sección

DATA(unique_list) = remove_duplicates( list ).

en lugar del innecesariamente largo

" anti-pattern
DATA(unique_list) = remove_duplicates( list = list ).

Sin embargo, hay algunos casos donde el nombre del método no es suficiente para que sea claro lo que se está haciendo, y el nombre del parámetro puede dar la claridad necesaria:

car->drive( speed = 50 ).
update( asynchronous = abap_true ).

Omite la referencia a sí mismo cuando llames un método de instancia

Clean ABAP > Contenido > Métodos > Calls > Esta sección

Ya que la referencia a la misma clase me-> se coloca implícitamente por el sistema, omite la sentencia cuando llames un método de instancia

DATA(sum) = aggregate_values( values ).

en lugar del innecesariamente largo

" anti-pattern
DATA(sum) = me->aggregate_values( values ).

Methods: Object orientation

Clean ABAP > Contenido > Métodos > Esta sección

Prefiere métodos de instancia a estáticos

Clean ABAP > Contenido > Métodos > Métodos: Orientación a objetos > Esta sección

Los métodos deben ser miembros de instancia por default. Los métodos de instancia reflejan mejor el estado de objeto de la clase. Se puede crear un mock de ellos en pruebas unitarias.

METHODS publish.

Los métodos debe ser estáticos únicamente en casos excepcionales, como métodos de creación estática.

CLASS-METHODS create_instance
  RETURNING
    VALUE(result) TYPE REF TO /clean/blog_post.

Los métodos de instancia públicos deben ser parte de una interfaz

Clean ABAP > Contenido > Métodos > Métodos: Orientación a objetos > Esta sección

Los métodos de instancia públicos deben siempre ser parte de una interfaz. Esto desacopla dependencias y simplifica crear un mock de ellos en pruebas unitarias.

METHOD /clean/blog_post~publish.

En la orientación a objetos limpia, tener un método público sin una interfaz no hace sentido - con algunas excepciones como clases de enumeración, que nunca tendrán una implementación alterna y nunca se les creará un mock.

Interfaces vs. clases abstractas describe porque esto aplica también a las clases que sobre-escriben métodos heredados.

Número de parámetros

Clean ABAP > Contenido > Métodos > Esta sección

Procura usar pocos parámetros IMPORTING, menos de tres es lo ideal

Clean ABAP > Contenido > Métodos > Número de parámetros > Esta sección

FUNCTION seo_class_copy
  IMPORTING
    clskey      TYPE seoclskey
    new_clskey  TYPE seoclskey
    config      TYPE class_copy_config
  EXPORTING
    ...

sería mucho más claro que

" anti-pattern
FUNCTION seo_class_copy
  IMPORTING
    clskey                 TYPE seoclskey
    new_clskey             TYPE seoclskey
    access_permission      TYPE seox_boolean DEFAULT seox_true
    VALUE(save)            TYPE seox_boolean DEFAULT seox_true
    VALUE(suppress_corr)   TYPE seox_boolean DEFAULT seox_false
    VALUE(suppress_dialog) TYPE seox_boolean DEFAULT seox_false
    VALUE(authority_check) TYPE seox_boolean DEFAULT seox_true
    lifecycle_manager      TYPE REF TO if_adt_lifecycle_manager OPTIONAL
    lock_handle            TYPE REF TO if_adt_lock_handle OPTIONAL
    VALUE(suppress_commit) TYPE seox_boolean DEFAULT seox_false
  EXPORTING
    ...

Muchos parámetros de entrada permiten que la complejidad de un método explote porque necesita manejar un número exponencial de combinaciones. Muchos parámetros son un indicador de que el método puede estar haciendo más de una sola cosa.

Puedes reducir el número de parámetros combinándolos en conjuntos que muestran su significado a través de estructuras y objetos.

Separa métodos en lugar de agregar parámetros OPTIONAL

Clean ABAP > Contenido > Métodos > Número de parámetros > Esta sección

METHODS do_one_thing IMPORTING what_i_need TYPE string.
METHODS do_another_thing IMPORTING something_else TYPE i.

para lograr la semántica deseada, ya que ABAP no soporta la sobrecarga.

" anti-pattern
METHODS do_one_or_the_other
  IMPORTING
    what_i_need    TYPE string OPTIONAL
    something_else TYPE i OPTIONAL.

Los parámetros opcionales confunden a los consumidores:

  • ¿Cuáles se requieren de verdad?
  • ¿Cuáles combinaciones son válidas?
  • ¿Cuáles excluyen otros parámetros?

Se puede evitar esta confusión teniendo múltiples métodos con parámetros específicos, dando una guía clara de que combinaciones de parámetros son validas y esperadas.

Usa PREFERRED PARAMETER con mesura

Clean ABAP > Contenido > Métodos > Número de parámetros > Esta sección

La adición PREFERRED PARAMETER hace difícil ver cuál parámetro se está enviando, lo que hace más difícil entender el código. Minimizar el número de parámetros, especialmente los opcionales, automáticamente reduce la necesidad de PREFERRED PARAMETER.

Usa RETURNING, EXPORTING y CHANGING para exactamente un parámetro

Clean ABAP > Contenido > Métodos > Número de parámetros > Esta sección

Un buen método hace una sola cosa y eso se debería ver reflejado en que solo debe retornar exactamente una cosa. Si los parámetros de salida de tu método no forman una entidad lógica, tu método está haciendo más de una cosa y debes dividirlo.

Hay casos donde la salida es una entidad lógica que consiste de múltiples entidades. Este caso es fácilmente representado retornando una estructura o un objeto:

TYPES:
  BEGIN OF check_result,
    result      TYPE result_type,
    failed_keys TYPE /bobf/t_frw_key,
    messages    TYPE /bobf/t_frw_message,
  END OF check_result.

METHODS check_business_partners
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE check_result.

en lugar de

" anti-pattern
METHODS check_business_partners
  IMPORTING
    business_partners TYPE business_partners
  EXPORTING
    result            TYPE result_type
    failed_keys       TYPE /bobf/t_frw_key
    messages          TYPE /bobf/t_frw_message.

Especialmente en comparación a múltiples parámetros EXPORTING, esto permite a la gente usar el método funcional de llamado de métodos, evitando tener que pensar en usar IS SUPPLIED y ayuda a las personas de accidentalmente olvidar obtener información vital de ERROR_OCCURRED.

En lugar de múltiples parámetros opcionales de salida, considera dividir el método de acuerdo a patrones de llamada que hagan sentido:

TYPES:
  BEGIN OF check_result,
    result      TYPE result_type,
    failed_keys TYPE /bobf/t_frw_key,
    messages    TYPE /bobf/t_frw_message,
  END OF check_result.

METHODS check
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE result_type.

METHODS check_and_report
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE check_result.

Tipos de parámetros

Clean ABAP > Contenido > Métodos > Esta sección

Prefiere RETURNING en lugar de EXPORTING

Clean ABAP > Contenido > Métodos > Tipos de parámetros > Esta sección

METHODS square
  IMPORTING
    number        TYPE i
  RETURNING
    VALUE(result) TYPE i.

DATA(result) = square( 42 ).

En lugar del innecesariamente largo

" anti-pattern
METHODS square
  IMPORTING
    number TYPE i
  EXPORTING
    result TYPE i.

square(
  EXPORTING
    number = 42
  IMPORTING
    result = DATA(result) ).

RETURNING no solo hace la llamada más corta, habilita el encadenamiento de métodos y previene errores cuando la entrada y salida son iguales.

No hay problema generalmente con usar RETURNING para tablas grandes

Clean ABAP > Contenido > Métodos > Tipos de parámetros > Esta sección

Aunque la documentación de ABAP y las guías de rendimiento dicen otra cosa, rara vez hemos encontrado un caso donde una tabla grande o con estructura profunda en un parámetro VALUE realmente causen problemas de rendimiento. Por lo tanto recomendamos usar generalmente

METHODS get_large_table
  RETURNING
    VALUE(result) TYPE /clean/some_table_type.

METHOD get_large_table.
  result = me->large_table.
ENDMETHOD.

DATA(my_table) = get_large_table( ).

Únicamente si hay prueba (= una medición de mal rendimiento) para tu caso individual es que deberías recurrir al estilo procedural

" anti-pattern
METHODS get_large_table
  EXPORTING
    result TYPE /dirty/some_table_type.

METHOD get_large_table.
  result = me->large_table.
ENDMETHOD.

get_large_table( IMPORTING result = DATA(my_table) ).

Esta sección contradice las ABAP Programming Guidelines y las revisiones de Code Inspector, donde ambos sugieren que las tablas grandes deben estar en la sección EXPORTING para evitar problemas de rendimiento. Hemos fallado consistentemente en reproducir problemas de rendimiento y/o memoria y notado que hay optimizaciones de kernel que generalmente mejoran el rendimiento usando la sentencia RETURNING.

Usa solo RETURNING o EXPORTING, o CHANGING, pero no en conjunto

Clean ABAP > Contenido > Métodos > Tipos de parámetros > Esta sección

METHODS copy_class
  IMPORTING
    old_name      TYPE seoclsname
    new name      TYPE secolsname
  RETURNING
    VALUE(result) TYPE copy_result
  RAISING
    /clean/class_copy_failure.

en lugar de confundir con mezclas como

" anti-pattern
METHODS copy_class
  ...
  RETURNING
    VALUE(result)      TYPE vseoclass
  EXPORTING
    error_occurred     TYPE abap_bool
  CHANGING
    correction_request TYPE trkorr
    package            TYPE devclass.

Diferentes tipos de parámetros de salida es un indicador de que el método hace más de una sola cosa. Confunde al lector y hace llamar al método innecesariamente complicado.

Una excepción aceptable a esta regla son los builders que consumen su entrada mientras construyen su salida.

METHODS build_tree
  CHANGING
    tokens        TYPE tokens
  RETURNING
    VALUE(result) TYPE REF TO tree.

Sin embargo, incluso estos casos se pueden volver más claros convirtiendo la entrda en un objeto:

METHODS build_tree
  IMPORTING
    tokens        TYPE REF TO token_stack
  RETURNING
    VALUE(result) TYPE REF TO tree.

Usa CHANGING con mesura, donde aplique

Clean ABAP > Contenido > Métodos > Tipos de parámetros > Esta sección

CHANGING debe estar reservado para casos donde una variable local existente que ya está siendo llenada es actualizada solo en algunos lugares:

METHODS update_references
  IMPORTING
    new_reference TYPE /bobf/conf_key
  CHANGING
    bo_nodes      TYPE root_nodes.

METHOD update_references.
  LOOP AT bo_nodes REFERENCE INTO DATA(bo_node).
    bo_node->reference = new_reference.
  ENDLOOP.
ENDMETHOD.

No forces a tus consumidores a introducir variables locales innecesarias solo para llenar tu parámetro CHANGING. No uses parámetros CHANGING para llenar una variable previamente vacía.

Separa los métodos, en lugar de recibir parámetros booleanos de entrada

Clean ABAP > Contenido > Métodos > Tipos de parámetros > Esta sección

Los parámetros de entrada booleanos son seguid un indicador de que un método hace dos cosas en lugar de una.

" anti-pattern
METHODS update
  IMPORTING
    do_save TYPE abap_bool.

Además, las llamadas a métodos con un solo parámetro booleano tienden a esconder el significado del parámetro

" anti-pattern
update( abap_true ).  " what does 'true' mean? synchronous? simulate? commit?

Dividir el método puede simplificar el código del método y describir mejor las intenciones

update_without_saving( ).
update_and_save( ).

La percepción común sugiere que los setters para variables booleanas son aceptables:

METHODS set_is_deleted
  IMPORTING
    new_value TYPE abap_bool.

Lee más en 1 2 3

Nombres de parámetros

Clean ABAP > Contenido > Métodos > Esta sección

Considera llamar RESULT al parámetro RETURNING

Clean ABAP > Contenido > Métodos > Nombres de parámetros > Esta sección

Los buenos nombres para métodos son usualmente tan buenos que el parámetro RETURNING no necesita su propio nombre. El nombre lograría poco menos que repetir el nombre del método o algo obvio.

Repetir el nombre de un miembro puede incluso producir conflictos que necesitan ser resueltos agregando un superfluo me->

" anti-pattern
METHODS get_name
  RETURNING
    VALUE(name) TYPE string.

METHOD get_name.
  name = me->name.
ENDMETHOD.

En estos casos, simplemente llama al parámetro RESULT, o algo como R_RESULT si prefieres la notación Húngara.

Nombra el parámetro RETURNING si no es obvio su objetivo, por ejemplo en métodos que retornan la instancia me para encadenamiento de métodos, o en métodos que crean algo pero no retornan la entidad creada, sino solo su llave.

Inicialización de parámetros

Clean ABAP > Contenido > Métodos > Esta sección

Clear or overwrite EXPORTING reference parameters

Clean ABAP > Contenido > Métodos > Inicialización de parámetros > Esta sección

Los parámetros por referencia apuntan a áreas de memoria existentes que pueden estar llenas de antemano. Limpia o sobre-escribelos para proveer información confiable.

METHODS square
  EXPORTING
    result TYPE i.

" clear
METHOD square.
  CLEAR result.
  " ...
ENDMETHOD.

" overwrite
METHOD square.
  result = cl_abap_math=>square( 2 ).
ENDMETHOD.

El Code Inspector and Checkman marcan variables EXPORTING que nunca se les asigna un valor. Usa estas revisiones estáticas para evitar este error difícil de resolver.

[Cuida si la entrada y la salida pueden ser lo mismo]

Clean ABAP > Contenido > Métodos > Inicialización de parámetros > Esta sección

Generalmente, es una buena idea limpiar el parámetro como el primer paso después de la declaración de datos y tipos. Esto hace la sentencia fácil de ubicar y evita que el valor que aún está contenido pueda ser usado accidentalmente en sentencias posteriores.

Sin embargo, algunas configuraciones de parámetro podrían usar la misma variable como entrada y como salida.

En este caso, un CLEAR borraría el valor de entrada antes de que pueda ser usado, produciendo resultados erróneos.

" anti-pattern
DATA value TYPE i.

square_dirty(
  EXPORTING
    number = value
  IMPORTING
    result = value ).

METHOD square_dirty.
  CLEAR result.
  result = number * number.
ENDMETHOD.

Considera rediseñar estos métodos reemplazando EXPORTING con RETURNING. También considera sobre-escribir el parámetro EXPORTING en una sola sentencia de cálculo. Si ninguna se adecua al requerimiento, utiliza un CLEAR tardío.

No hagas CLEAR a parámetros VALUE

Clean ABAP > Contenido > Métodos > Inicialización de parámetros > Esta sección

Los parámetros que funcionan por VALUE son entregados como áreas de memoria nuevas y separadas, que están vacías por definición. No las limpies de nuevo:

METHODS square
  EXPORTING
    VALUE(result) TYPE i.

METHOD square.
  " no need to CLEAR result
ENDMETHOD.

Los parámetros RETURNING son siempre del tipo VALUE, así que nunca es necesario hacerles un CLEAR:

METHODS square
  RETURNING
    VALUE(result) TYPE i.

METHOD square.
  " no need to CLEAR result
ENDMETHOD.

Cuerpo del método

Clean ABAP > Contenido > Métodos > Esta sección

Haz una cosa, hazla bien, no hagas más que eso

Clean ABAP > Contenido > Métodos > Cuerpo del método > Esta sección

Un método debería hacer una cosa y una sola cosa. Y lo debe hacer de la mejor manera posible.

Un método probablemente hace una cosa si:

Enfócate en el happy path o en manejo de errores, no en ambos

Clean ABAP > Contenido > Métodos > Cuerpo del método > Esta sección

Como una especialización de la regla Haz una cosa, hazla bien, no hagas más que eso, un método debería seguir el happy path para el que fue construido o el camino de manejo de errores en caso de que no pueda, pero probablemente no ambos.

" anti-pattern
METHOD append_xs.
  IF input > 0.
    DATA(remainder) = input.
    WHILE remainder > 0.
      result = result && `X`.
      remainder = remainder - 1.
    ENDWHILE.
  ELSEIF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSE.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

Puede ser dividido en

METHOD append_xs.
  validate( input ).
  DATA(remainder) = input.
  WHILE remainder > 0.
    result = result && `X`.
    remainder = remainder - 1.
  ENDWHILE.
ENDMETHOD.

METHOD validate.
  IF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSEIF input < 0.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

o, para recalcar la parte de la validación

METHOD append_xs.
  IF input > 0.
    result = append_xs_without_check( input ).
  ELSEIF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSE.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

METHOD append_xs_without_check.
  DATA(remainder) = input.
  WHILE remainder > 0.
    result = result && `X`.
    remainder = remainder - 1.
  ENDWHILE.
ENDMETHOD.

Desciende un nivel de abstracción

Clean ABAP > Contenido > Métodos > Cuerpo del método > Esta sección

Las sentencias en un método deberían ser un nivel de abstracción más abajo que el método mismo. De la misma manera, todas las sentencias deberían estar en el mismo nivel de abstracción.

METHOD create_and_publish.
  post = create_post( user_input ).
  post->publish( ).
ENDMETHOD.

en lugar de mezclas confusas de sentencias de bajo nivel (trim, to_upper, ...) y alto nivel (publish, ...) como

" anti-pattern
METHOD create_and_publish.
  post = NEW blog_post( ).
  DATA(user_name) = trim( to_upper( sy-uname ) ).
  post->set_author( user_name ).
  post->publish( ).
ENDMETHOD.

Una manera confiable de encontrar cuál es el nivel de abstracción adecuado es la siguiente: Permite al autor del método explicar lo que hace el método en pocas palabras, sin que vea el código. Los incisos que mencione son los submétodos que el método debería de llamar o las sentencias que debería ejecutar.

Mantén los métodos cortos

Clean ABAP > Contenido > Métodos > Cuerpo del método > Esta sección

Methods should have less than 20 statements, optimal around 3 to 5 statements.

METHOD read_and_parse_version_filters.
  DATA(active_model_version) = read_random_version_under( model_guid ).
  DATA(filter_json) = read_model_version_filters( active_model_version-guid ).
  result = parse_model_version_filters( filter_json ).
ENDMETHOD.

La siguiente declaración DATA por si misma es suficiente para ver que el método que la contiene hace más de una cosa:

" anti-pattern
DATA:
  class           TYPE vseoclass,
  attributes      TYPE seoo_attributes_r,
  methods         TYPE seoo_methods_r,
  events          TYPE seoo_events_r,
  types           TYPE seoo_types_r,
  aliases         TYPE seoo_aliases_r,
  implementings   TYPE seor_implementings_r,
  inheritance     TYPE vseoextend,
  friendships     TYPE seof_friendships_r,
  typepusages     TYPE seot_typepusages_r,
  clsdeferrds     TYPE seot_clsdeferrds_r,
  intdeferrds     TYPE seot_intdeferrds_r,
  attribute       TYPE vseoattrib,
  method          TYPE vseomethod,
  event           TYPE vseoevent,
  type            TYPE vseotype,
  alias           TYPE seoaliases,
  implementing    TYPE vseoimplem,
  friendship      TYPE seofriends,
  typepusage      TYPE vseotypep,
  clsdeferrd      TYPE vseocdefer,
  intdeferrd      TYPE vseoidefer,
  new_clskey_save TYPE seoclskey.

Por supuesto hay ocasiones donde no hace sentido reducir un método grande más de lo que ya está. Esto es correcto siempre que el método haga una sola cosa:

METHOD decide_what_to_do.
  CASE temperature.
    WHEN burning.
      result = air_conditioning.
    WHEN hot.
      result = ice_cream.
    WHEN moderate.
      result = chill.
    WHEN cold.
      result = skiing.
    WHEN freezing.
      result = hot_cocoa.
  ENDCASE.
ENDMETHOD.

Sin embargo, aún hace sentido validar si el código extenso esconde un patrón más adecuado:

METHOD decide_what_to_do.
  result = VALUE #( spare_time_activities[ temperature = temperature ] OPTIONAL ).
ENDMETHOD.

Dividir los métodos hasta hacerlos muy pequeños puede tener un impacto negativo en el rendimiento al incrementar el número de llamadas a métodos. La sección Considera el rendimiento provee guía en cómo balancear el Código Limpio y el rendimiento.

Flujo de control

Clean ABAP > Contenido > Métodos > Esta sección

Falla rápido

Clean ABAP > Contenido > Métodos > Flujo de control > Esta sección

Valida y falla tan pronto como sea posible.

METHOD do_something.
  IF input IS INITIAL.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
  DATA(massive_object) = build_expensive_object_from( input ).
  result = massive_object->do_some_fancy_calculation( ).
ENDMETHOD.

Las validaciones que se hacen después son más difíciles de ubicar y pueden haber malgastado recursos para llegar a ese punto.

" anti-pattern
METHOD do_something.
  DATA(massive_object) = build_expensive_object_from( input ).
  IF massive_object IS NOT BOUND. " happens if input is initial
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
  result = massive_object->do_some_fancy_calculation( ).
ENDMETHOD.

CHECK vs. RETURN

Clean ABAP > Contenido > Métodos > Flujo de control > Esta sección

No hay un consenso acerca de si debes usar CHECK o RETURN para salir de un método si las entradas no cumplen con los requisitos.

Mientras que CHECK definitivamente provee una sintaxis más corta

METHOD read_customizing.
  CHECK keys IS NOT INITIAL.
  " do whatever needs doing
ENDMETHOD.

el nombre de la sentencia no revela lo que pasa si la condición falla, de manera que las personas probablemente entenderán la forma larga mejor:

METHOD read_customizing.
  IF keys IS INITIAL.
    RETURN.
  ENDIF.
  " do whatever needs doing
ENDMETHOD.

Puedes evitar la pregunta completamente invirtiendo la validación y adoptando un flujo de control de un solo retorno

METHOD read_customizing.
  IF keys IS NOT INITIAL.
    " do whatever needs doing
  ENDIF.
ENDMETHOD.

En cualquier caso, considera si no retornar algo es el comportamiento adecuado. Los métodos deben proveer un resultado con significado, lo que implica un parámetro de retorno con un valor asignado o una excepción. No retornar algo es en muchos casos similar a retornar null, lo que debería ser evitado.

La sección Exiting Procedures in the ABAP Programming Guidelines recomienda usar CHECK en este caso. La discusión en la comunidad sugiere que la sentencia no es tan clara, lo que causará que muchas personas no entiendan el comportamiento del programa.

Evita CHECK en otros lugares

Clean ABAP > Contenido > Métodos > Flujo de control > Esta sección

No uses CHECK fuera de la inicialización de un método. La sentencia se comporta diferente en diferentes posiciones y puede llevar a efectos no esperados.

Por ejemplo, CHECK en un LOOP termina la iteración actual y continúa con la siguiente; las personas podrían accidentalmente esperar que se termine la ejecución del método o que se salga del LOOP. Es preferible usar una sentencia IF en combinación con un CONTINUE, ya que CONTINUE solo puede usarse dentro de LOOP.

Basado en la sección Exiting Procedures en las ABAP Programming Guidelines. Observa que esto contradice la referencia para la palabra clave CHECK en loops.

Manejo de errores

Clean ABAP > Contenido > Esta sección

Mensajes

Clean ABAP > Contenido > Manejo de errores > Esta sección

Haz que los mensajes sean fáciles de encontrar

Clean ABAP > Contenido > Manejo de errores > Mensajes > Esta sección

Para hacer los mensajes fáciles de encontrar en una búsqueda desde la transacción SE91, utiliza el siguiente patrón:

MESSAGE e001(ad) INTO DATA(message).

En caso de que la variable message no sea requerida, agrega el pragma ##NEEDED:

MESSAGE e001(ad) INTO DATA(message) ##NEEDED.

Evita lo siguiente:

" anti-pattern
IF 1 = 2. MESSAGE e001(ad). ENDIF.

Este es un anti-patrón, ya que:

  • Contiene código al que nunca se va a llegar.
  • Prueba una condición que nunca puede ser TRUE.

Códigos de retorno

Clean ABAP > Contenido > Manejo de errores > Esta sección

Prefiere excepciones a códigos de retorno

Clean ABAP > Contenido > Manejo de errores > Códigos de retorno > Esta sección

METHOD try_this_and_that.
  RAISE EXCEPTION NEW cx_failed( ).
ENDMETHOD.

en lugar de

" anti-pattern
METHOD try_this_and_that.
  error_occurred = abap_true.
ENDMETHOD.

Las excepciones tienen múltiples ventajas sobre los códigos de retorno:

  • Las excepciones mantienen el prototipo de tu método limpio: puedes retornar el resultado de un método como un parámetro RETURNING y aún así lanzar excepciones al mismo tiempo. Los códigos de retorno contaminan tus prototipos con parámetros adicionales para manejo de errores.

  • El consumidor no tiene que reaccionar a ellos inmediatamente. Puede simplemente escribir el happy path de su código. La palabra para manejo de excepciones CATCH puede estar hasta el final de su método o incluso afuera.

  • Las excepciones pueden proveer detalles del error en sus atributos y a través de métodos. Los códigos de retorno requieren que encuentres una solución diferente por tu cuenta, como también regresar un log.

  • El ambiente le recuerda al consumidor con errores de sintaxis que tiene que manejar excepciones. Los códigos de retorno pueden ser accidentalmente ignorados sin que se dé cuenta alguien.

No dejes pasar los errores

Clean ABAP > Contenido > Manejo de errores > Return Codes > Esta sección

Si tienes que usar códigos de retorno, por ejemplo porque usas módulos de funciones y código que no está bajo tu control, asegúrate de no dejar que los errores se escapen.

DATA:
  current_date TYPE string,
  response     TYPE bapiret2.

CALL FUNCTION 'BAPI_GET_CURRENT_DATE'
  IMPORTING
    current_date = current_date
  CHANGING
    response     = response.

IF response-type = 'E'.
  RAISE EXCEPTION NEW /clean/some_error( ).
ENDIF.

Excepciones

Clean ABAP > Contenido > Manejo de errores > Esta sección

Las excepciones son para errores, no para casos regulares

Clean ABAP > Contenido > Manejo de errores > Excepciones > Esta sección

" anti-pattern
METHODS entry_exists_in_db
  IMPORTING
    key TYPE char10
  RAISING
    cx_not_found_exception.

Si algo es un caso regular y válido, debería ser manejado a través de parámetros de salida regulares.

METHODS entry_exists_in_db
  IMPORTING
    key           TYPE char10
  RETURNING
    VALUE(result) TYPE abap_bool.

Las excepciones deben estar reservadas para casos que no esperas y que reflejan situaciones de error.

METHODS assert_user_input_is_valid
  IMPORTING
    user_input TYPE string
  RAISING
    cx_bad_user_input.

Usar mal las excepciones hace pensar al lector que algo falló, cuando en realidad todo está bien. Las excepciones son además mucho más lentas que el código regular porque necesitan ser construidas y generalmente acumulan mucha información de contexto del sistema.

Usa excepciones basadas en clases

Clean ABAP > Contenido > Manejo de errores > Excepciones > Esta sección

TRY.
    get_component_types( ).
  CATCH cx_has_deep_components_error.
ENDTRY.

Las excepciones clásicas no basadas en clases tienen las mismas características que los códigos de retorno y ya no deberían ser utilizadas.

" anti-pattern
get_component_types(
  EXCEPTIONS
    has_deep_components = 1
    OTHERS              = 2 ).

Lanzamiento de excepciones

Clean ABAP > Contenido > Manejo de errores > Esta sección

Usa tus propias súper clases

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

CLASS cx_fra_static_check DEFINITION ABSTRACT INHERITING FROM cx_static_check.
CLASS cx_fra_no_check DEFINITION ABSTRACT INHERITING FROM cx_no_check.

Considera crear súper clases abstractas para cada tipo de excepción para tu aplicación, en lugar de hacer una heredar de las clases principales de excepción. Esto te permite manejar con un CATCH todas tus excepciones. Te permite también agregar funcionalidad común a todas tus excepciones, como manejo especial de texto. El ABSTRACT previene que las personas usen accidentalmente estos errores no descriptivos directamente.

Lanza un solo tipo de excepción

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

METHODS generate
  RAISING
    cx_generation_error.

En la gran mayoría de los casos, lanzar múltiples tipos de excepción no tiene uso. El consumidor usualmente no está interesado ni es capaz de distinguir las situaciones de error. Por lo tanto, típicamente las va a manejar todas de la misma manera - si este es el caso, para que distinguirlas en primer lugar?

" anti-pattern
METHODS generate
  RAISING
    cx_abap_generation
    cx_hdbr_access_error
    cx_model_read_error.

Una mejor solución para reconocer diferentes situaciones de error es usar un tipo de excepción, pero agregar sub-clases que permitan (pero no requieran) reaccionar a situaciones individuales de error, como está descrito en Usa sub-clases para permitir que el usuario de la clase distinga situaciones de error.

Usa sub-clases para permitir que el usuario de la clase distinga situaciones de error

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

CLASS cx_bad_generation_variable DEFINITION INHERITING FROM cx_generation_error.
CLASS cx_bad_code_composer_template DEFINITION INHERITING FROM cx_generation_error.

TRY.
    generator->generate( ).
  CATCH cx_bad_generation_variable.
    log_failure( ).
  CATCH cx_bad_code_composer_template INTO DATA(bad_template_exception).
    show_error_to_user( bad_template_exception ).
  CATCH cx_generation_error INTO DATA(other_exception).
    RAISE EXCEPTION NEW cx_application_error( previous =  other_exception ).
ENDTRY.

Si hay varias situaciones de error, usa códigos de error en su lugar:

CLASS cx_generation_error DEFINITION ...
  PUBLIC SECTION.
    TYPES error_code_type TYPE i.
    CONSTANTS:
      BEGIN OF error_code_enum,
        bad_generation_variable    TYPE error_code_type VALUE 1,
        bad_code_composer_template TYPE error_code_type VALUE 2,
        ...
      END OF error_code_enum.
    DATA error_code TYPE error_code_type.

TRY.
    generator->generate( ).
  CATCH cx_generation_error INTO DATA(exception).
    CASE exception->error_code.
      WHEN cx_generation_error=>error_code_enum-bad_generation_variable.
      WHEN cx_generation_error=>error_code_enum-bad_code_composer_variable.
      ...
    ENDCASE.
ENDTRY.

Lanza CX_STATIC_CHECK para excepciones que se pueden manejar

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

Si una excepción se espera que puede ocurrir y que puede razonablemente ser manejada por el receptor, lanza una excepción con revisión, heredando de CX_STATIC_CHECK: fallar validación de parámetros de entrada, un recurso faltante para el cual hay una solución, etc.

CLASS cx_file_not_found DEFINITION INHERITING FROM cx_static_check.

METHODS read_file
  IMPORTING
    file_name_enterd_by_user TYPE string
  RAISING
    cx_file_not_found.

Este tipo de escepción debe aparecer en el prototipo del método y debe ser atrapado o propagado para evitar errores de sintaxis. Por lo tanto es claramente visible para el consumidor y asegura que no será sorprendido por una excepción inesperada y podrá hacerse cargo de la situación de error.

Esto está en sincronización con las ABAP Programming Guidelines pero contradice [Código Limpio por Robert C. Martin], que recomienda preferir excepciones sin revisión; Excepciones explica por qué.

Lanza CX_NO_CHECK para situaciones de las que típicamente no se puede recuperar

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

Si una excepción es tan severa que es poco probable que el receptor se recupere, usa CX_NO_CHECK: error al leer un recurso imprescindible, error al resolver la dependencia solicitada, etc.

CLASS cx_out_of_memory DEFINITION INHERITING FROM cx_no_check.

METHODS create_guid
  RETURNING
    VALUE(result) TYPE /bobf/conf_key.

CX_NO_CHECK no puede ser declarada en el prototipo de los métodos, de tal manera que al ocurrir será una mala sorpresa para el consumidor. En el caso de situaciones de las cuales no se puede recuperar, esto está bien porque el consumidor no tendrá la capacidad de hacer algo al respecto.

Sin embargo, puede haber casos donde el consumidor quiere reconocer y reaccionar a este tipo de error. Por ejemplo, un administrador de dependencias podría lanzar un CX_NO_CHECK si no le es posible proveer una implementación para una interfaz requerida porque el código de la aplicación no podría continuar Sin embargo, puede haber un reporte de prueba que trata de crear una instancia de todos los tipos, solo para ver si funcionan; el cual reportará el fallo simplemente como una entrada en rojo en una lista - este servicio debería poder atrapar e ignorar la excepción en lugar de ser forzado a lanzar un dump.

Considera CX_DYNAMIC_CHECK para excepciones que no se pueden evitar

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

Los casos de uso para CX_DYNAMIC_CHECK son raros, y en general recomendamos usar los otros tipos de excepción. Sin embargo, puedes querer considerar este tipo de excepción como un reemplazo para CX_STATIC_CHECK si el consumidor tiene control completo y consciente de que una excepción puede ocurrir.

DATA value TYPE decfloat.
value = '7.13'.
cl_abap_math=>get_db_length_decs(
  EXPORTING
    in     = value
  IMPORTING
    length = DATA(length) ).

Por ejemplo, considera el método get_db_length_decs de la clasecl_abap_math, que te dice el número de dígitos y decimales de un número de punto flotante. Este método lanza la excepción dinámica cx_parameter_invalid_type si el parámetro de entrada no representa un número de punto flotante. Usualmente, este método será llamado para una variable estática y completamente tipificada, de tal manera que el desarrollador sabe si la excepción puede ocurrir o no. La excepción dinámica permitiría al consumidor omitir la sentencia CATCH que no es necesaria en este caso.

Lanza un dump para situaciones que son completamente irrecuperables

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

Si una situación es tan severa que estás totalmente seguro que el consumidor no se podrá recuperar de ella, o que claramente indica un error de programación, lanza un dump en lugar de una excepción: error al adquirir memoria, lectura fallida de índices en una tabla que debe estar llena, etc.

RAISE SHORTDUMP TYPE cx_sy_create_object_error.  " >= NW 7.53
MESSAGE x666(general).                           " < NW 7.53

This behavior will prevent any kind of consumer from doing anything useful afterwards. Use this only if you are sure about that.

Prefiere RAISE EXCEPTION NEW en lugar de RAISE EXCEPTION TYPE

Clean ABAP > Contenido > Manejo de errores > Lanzamiento de excepciones > Esta sección

Nota: Disponible a partir de NW 7.52.

RAISE EXCEPTION NEW cx_generation_error( previous = exception ).

en general es más corto que el innecesariamente largo

RAISE EXCEPTION TYPE cx_generation_error
  EXPORTING
    previous = exception.

Sin embargo, si haces mucho uso de la adición MESSAGE, podrías querer mantener el uso con la variante TYPE:

RAISE EXCEPTION TYPE cx_generation_error
  EXPORTING
    previous = exception
  MESSAGE e136(messages).

Atrapando excepciones

Clean ABAP > Contenido > Manejo de errores > Esta sección

Wrap foreign exceptions instead of letting them invade your code

Clean ABAP > Contenido > Manejo de errores > Atrapando excepciones > [Esta sección]Envuelve excepciones foráneas en lugar de permitir que invadan tu código

METHODS generate RAISING cx_generation_failure.

METHOD generate.
  TRY.
      generator->generate( ).
    CATCH cx_amdp_generation_failure INTO DATA(exception).
      RAISE EXCEPTION NEW cx_generation_failure( previous = exception ).
  ENDTRY.
ENDMETHOD.

La Ley de Demeter recomienda desacoplar las cosas. Propagar excepciones de otros componentes viola este principio. Vuélvete independiente de código externo atrapando estas excepciones y propagándolas con tu propia excepción.

" anti-pattern
METHODS generate RAISING cx_sy_gateway_failure.

METHOD generate.
  generator->generate( ).
ENDMETHOD.

Comentarios

Clean ABAP > Contenido > Esta sección

Exprésate en código, no en comentarios

Clean ABAP > Contenido > Comentarios > Esta sección

METHOD correct_day_to_last_in_month.
  WHILE is_invalid( date ).
    reduce_day_by_one( CHANGING date = date ).
  ENDWHILE.
ENDMETHOD.

METHOD is_invalid.
  DATA zero_if_invalid TYPE i.
  zero_if_invalid = date.
  result = xsdbool( zero_if_invalid = 0 ).
ENDMETHOD.

METHOD reduce_day_by_one.
  date+6(2) = date+6(2) - 1.
ENDMETHOD.

en lugar de

" anti-pattern
" correct e.g. 29.02. in non-leap years as well as result of a date calculation would be
" something like e.g. the 31.06. that example has to be corrected to 30.06.
METHOD fix_day_overflow.
  DO 3 TIMES.
    " 31 - 28 = 3 => this correction is required not more than 3 times
    lv_dummy = cv_date.
    " lv_dummy is 0 if the date value is a not existing date - ABAP specific implementation
    IF ( lv_dummy EQ 0 ).
      cv_date+6(2) = cv_date+6(2) - 1. " subtract 1 day from the given date
    ELSE.
      " date exists => no correction required
      EXIT.
    ENDIF.
  ENDDO.
ENDMETHOD.

Código Limpio no te prohibe comentar tu código - te anima a explotar mejores maneras y recurrir a comentarios únicamente si éstas fallan.

Este ejemplo ha sido señalado desde un punto de vista de rendimiento, mencionando que dividir tan pequeños los métodos lo empeora fuertemente. Mediciones de prueba demuestran que el código refactorizado es 2.13 veces más lento que la variante original. La variante limpia toma 9.6 microsegundos para arreglar la entrada 31-02-2018, la variante no limpia toma 4.5 microsegundos. Este puede ser un problema cuando el método se corre muy seguido en una aplicación de alto rendimiento; para validación de parámetros de entrada regular, es aceptable. Recurre a la sección Cuida el rendimiento para atender temas relacionados con Código Limpio y problemas de rendimiento.

Los comentarios no son excusa para nombrar mal objetos

Clean ABAP > Contenido > Comentarios > Esta sección

DATA(input_has_entries) = has_entries( input ).

Improve your names instead of explaining what they really mean or why you chose bad ones.

" anti-pattern
" checks whether the table input contains entries
DATA(result) = check_table( input ).

Usa métodos en lugar de comentarios para segmentar tu código

Clean ABAP > Contenido > Comentarios > Esta sección

DATA(statement) = build_statement( ).
DATA(data) = execute_statement( statement ).

Esto no solo muestra la intención, estructura y dependencias mucho más claramente, también evita acarrear errores cuando variables temporales no son limpiadas adecuadamente entre secciones.

" anti-pattern
" -----------------
" Build statement
" -----------------
DATA statement TYPE string.
statement = |SELECT * FROM d_document_roots|.

" -----------------
" Execute statement
" -----------------
DATA(result_set) = adbc->execute_sql_query( statement ).
result_set->next_package( IMPORTING data = data ).

Escribe comentarios para explicar el por qué, no el qué

Clean ABAP > Contenido > Comentarios > Esta sección

" can't fail, existence of >= 1 row asserted above
DATA(first_line) = table[ 1 ].

Nadie necesita repetir el código en lenguaje natural

" anti-pattern
" select alert root from database by key
SELECT * FROM d_alert_root WHERE key = key.

El diseño va en los documentos de diseño, no en el código

Clean ABAP > Contenido > Comentarios > Esta sección

" anti-pattern
" This class serves a double purpose. First, it does one thing. Then, it does another thing.
" It does so by executing a lot of code that is distributed over the local helper classes.
" To understand what's going on, let us at first ponder the nature of the universe as such.
" Have a look at this and that to get the details.

Nadie lee eso - en serio. Si las personas necesitan leer una novela para usar tu código, esto puede ser un indicador de que tu código tiene problemas severos de diseño que deberías resolver. Hay código que sí necesita algo de explicación adicional más allá de una sola línea de comentario; considera colocar la liga del documento de diseño en estos casos.

Usa " para comentar, no *

Clean ABAP > Contenido > Comentarios > Esta sección

Los comentarios con comillas se indentan junto con las sentencias que están siendo comentadas

METHOD do_it.
  IF input IS NOT INITIAL.
    " delegate pattern
    output = calculate_result( input ).
  ENDIF.
ENDMETHOD.

Los comentarios con asteriscos tienden a indentarse de manera extraña

" anti-pattern
METHOD do_it.
  IF input IS NOT INITIAL.
* delegate pattern
    output = calculate_result( input ).
  ENDIF.
ENDMETHOD.

Usa comentarios antes de la sentencia a la que hacen referencia

Clean ABAP > Contenido > Comentarios > Esta sección

" delegate pattern
output = calculate_result( input ).

Más claro que

" anti-pattern
output = calculate_result( input ).
" delegate pattern

Y menos invasivo que

output = calculate_result( input ).  " delegate pattern

Borra el código en lugar de comentarlo

Clean ABAP > Contenido > Comentarios > Esta sección

" anti-pattern
* output = calculate_result( input ).

Cuando encuentres algo como esto, bórralo. Este código obviamente no se necesita, porque tu aplicación funciona y todos las pruebas están en verde. Código borrado puede ser recuperado del historial de versiones si se necesita. Si necesitas preservar una porción de código permanentemente, cópiala a un archivo a $TMP o a HOME.

Usa FIXME, TODO y XXX y agrega tu usuario

Clean ABAP > Contenido > Comentarios > Esta sección

METHOD do_something.
  " XXX FH delete this method - it does nothing
ENDMETHOD.
  • FIXME indica errores que son muy pequeños o muy lejanos al propósito del incidente.
  • TODO marca secciones donde quieres completar algo en el futuro(!) cercano.
  • XXX indica código que funciona, pero podría mejorarse.

Cuando introduzcas un comentario de este estilo, agrega tu sobrenombre, iniciales o usuario para permitir que otros desarrolladores te contacten y puedan hacer preguntas si el comentario no está claro.

No agregues prototipos ni comentarios de fin de métodos

Clean ABAP > Contenido > Comentarios > Esta sección

Los comentarios de prototipos de métodos no le sirven a nadie.

" anti-pattern
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method CALIBRATION_KPIS=>CALCULATE_KPI
* +-------------------------------------------------------------------------------------------------+
* | [--->] STRATEGY_ID                 TYPE        STRATEGY_ID
* | [--->] THRESHOLD                   TYPE        STRATEGY_THRESHOLD
* | [--->] DETECTION_OBJECT_SCORE      TYPE        T_HIT_RESULT
* | [<---] KPI                         TYPE        T_SIMULATED_KPI
* +--------------------------------------------------------------------------------------</SIGNATURE>

Hace décadas, cuando no podías ver el prototipo al inspeccionar el código o que trabajabas con impresiones con docenas de páginas, estos comentarios puede que hayan hecho sentido. Pero todos los IDEs modernos (SE24, SE80, ADT) muestran la firma del método fácilmente, de manera que estos comentarios no son nada más que ruido.

En el editor basado en formas de la SE24/SE80, presiona el botón Signature. En ABAP Development Tools, selecciona el nombre del método y presiona F2 o agrega la vista ABAP Element Info a tu perspectiva en Eclipse.

Similarmente, los comentarios de fin de método son súperfluos. Estos comentarios pueden haber sido útiles hace décadas, cuando los programas, las funciones y los IFs anidados estaban dentro de cientos de líneas de código. Pero nuestro estilo moderno de programación produce métodos lo suficientemente cortos para que se pueda ver fácilmente la sentencia inicial a la que pertenece un ENDIF o un ENDMETHOD:

" anti-pattern
METHOD get_kpi_calc.
  IF has_entries = abap_false.
    result = 42.
  ENDIF.  " IF has_entries = abap_false
ENDMETHOD.   " get_kpi_calc

No repitas los mensajes o textos en comentarios

Clean ABAP > Contenido > Comentarios > Esta sección

" anti-pattern
" alert category not filled
MESSAGE e003 INTO dummy.

Los mensajes van a cambiar independientemente de tu código y nadie va a recordar ajustar el comentario, de tal manera que se va a desactualizar y va a confundir a los desarrolladores rápidamente sin que nadie lo note.

Los IDEs modernos proveen maneras sencillas de ver el texto detrás de un mensaje, por ejemplo en ABAP Development Tools, seleccionas el ID de mensaje y presionas Shift + F2.

Si quieres que sea más explícito, considera extraer el mensaje a su propio método.

METHOD create_alert_not_found_message.
  MESSAGE e003 INTO dummy.
ENDMETHOD.

Usa ABAP Doc únicamente para APIs públicas

Clean ABAP > Contenido > Comentarios > Esta sección

Escribe ABAP Doc para documentar APIs públicas, lo que significa APIs que están dirigidas a otros desarrolladores en otros equipos o aplicaciones. No escribas ABAP Doc para material interno.

ABAP Doc sufre de las mismas debilidades que todos los comentarios, que se desactualizan rápidamente y solo confunden al desarrollador cuando pasa. Como consecuencia, solo deberías usar esta herramienta cuando hace sentido, no obligar a que se use para todos los casos

Lee más en Capítulo 4: Comentarios: Javadoc en APIs públicas y en Capítulo 4: Comentarios incorrectos: Javadocs in Nonpublic Code de [Código Limpio por Robert C. Martin].

Usa pragmas en lugar de pseudo-comentarios

Clean ABAP > Contenido > Comentarios > Esta sección

Prefiere el uso de pragmas a pseudo-comentarios para suprimir advertencias y errores irrelevantes identificados por ATC. Los pseudo-comentarios están en su mayoría obsoletos y han sido reemplazados por pragmas.

" pattern
MESSAGE e001(ad) INTO DATA(message) ##NEEDED.

" anti-pattern
MESSAGE e001(ad) INTO DATA(message). "#EC NEEDED

Usa el programa ABAP_SLIN_PRAGMAS o la tabla SLIN_DESC para encontrar el mapeo entre pseudo-comentarios obsoletos y los pragmas que los han reemplazado.

Formato

Clean ABAP > Contenido > Esta sección

Las sugerencias en esta sección están optimizadas para lectura, no para escritura. Ya que el Pretty Printer de ABAP no las cubre, algunas de ellas provocan trabajo adicional manual para re-formatear sentencias cuando las longitudes de los nombres cambian, etc.; si quieres evitar esto, considera no usar reglas como As ABAP's Pretty Printer doesn't cover them, some of them produce additional manual work to reformat statements when name lengths etc. change; if you want to avoid this, consider dropping rules like Alinea asignaciones al mismo objeto, pero no a objetos diferentes.

Sé consistente

Clean ABAP > Contenido > Esta sección

Dale formato a todo el código de un proyecto de la misma manera. Permite que todos los miembros del equipo usen el mismo estilo.

Si editas código externo, adhiérete al estilo de ese proyectom en lugar de insistir en tu estilo personal.

Si las reglas de formato cambian con el tiempo, usa las mejores prácticas de refactorización para actualizar tu código.

Optimiza para lectura, no para escritura

Clean ABAP > Contenido > Formato > Esta sección

Los desarrolladores pasan la mayor parte de su tiempo leyendo código. En realidad, escribir código toma parte de una porción muy pequeña del día.

Como consecuencia, deberías optimizar el formato de tu código para leer y hacer debugging, no para escribir.

Por ejemplo, prefiere

DATA:
  a TYPE b,
  c TYPE d,
  e TYPE f.

a

" anti-pattern
DATA:
  a TYPE b
  ,c TYPE d
  ,e TYPE f.

Usa el Pretty Printer antes de activar

Clean ABAP > Contenido > Formato > Esta sección

Aplica el Pretty Printer - Shift + F1 en SE80, SE24 y ADT - antes de activar un objeto.

Si modificas código legacy sin formato, puede que quieras aplicar el Pretty Printer solo a líneas selectas para evitar generar listas de cambios enormes y dependencias en transportes. Considera hacer Pretty Print en una orden de transporte individual o nota.

If you modify a larger unformatted legacy code base, you may want to apply the Pretty Printer only to selected lines to avoid huge change lists and transport dependencies. Consider pretty-printing the complete development object in a separate Transport Request or Note.

Lee más en Capítulo 5: Formato: Reglas de equipo de [Código Limpio por Robert C. Martin].

Usa la configuración de Pretty Printer de tu equipo

Clean ABAP > Contenido > Formato > Esta sección

Siempre usa la configuración de tu equipo. Configúralo en Menu > Utilities > Settings ... > ABAP Editor > Pretty Printer.

Configura Indent y Convert Uppercase/Lowercase > Uppercase Keyword según se haya acordado en tu equipo.

Mayúsculas vs. Minúsculas explica por qué no damos una guía clara sobre este tema.

Lee más en Capítulo 5: Formato: Reglas de equipo de [Código Limpio por Robert C. Martin].

No más de una sentencia por línea de código

Clean ABAP > Contenido > Formato > Esta sección

DATA do_this TYPE i.
do_this = input + 3.

Aún si algunas situaciones pueden llevarte a creer que esto es legible:

" anti-pattern
DATA do_this TYPE i. do_this = input + 3.

Mantén una longitud de línea razonable

Clean ABAP > Contenido > Formato > Esta sección

Mantén una longitud máxima de línea de 120 caracteres.

El ojo humano lee el texto más cómodamente si las líneas no son tan anchas - pregunta a un diseñador de UI o investigador de movimiento del ojo de tu confianza. También podrás apreciar mejor el código más angosto al hacer debugging o comparar dos fuentes de código una junto a la otra.

El límite de 80 o incluso 72 caracteres originado en las terminales viejas es muy restrictivo. Mientras que 100 caracteres son recomendados comúnmente y son una opción viable, para ABAP 120 funcionan un poco mejor, probablemente debido a la verbosidad del lenguaje.

Como recordatorio, puedes configurar ADT el margen a 120 caracteres que son visualizados en el código como una línea vertical. Configúralo en Menu > Window > Preferences > General > Editors > Text Editors.

Condensa tu código

Clean ABAP > Contenido > Formato > Esta sección

DATA(result) = calculate( items ).

en lugar de agregar espacios innecesarios

" anti-pattern
DATA(result)        =      calculate(    items =   items )   .

Usa una línea en blanco para separar cosas, pero no más

Clean ABAP > Contenido > Formato > Esta sección

DATA(result) = do_something( ).

DATA(else) = calculate_this( result ).

para resaltar que las dos sentencias hacen cosas diferentes. Pero no hay razón para

" anti-pattern
DATA(result) = do_something( ).



DATA(else) = calculate_this( result ).

La urgencia de agregar líneas en blanco puede ser un indicador de que tu método no hace una sola cosa.

No te obsesiones con separar usando líneas en blanco

Clean ABAP > Contenido > Formato > Esta sección

METHOD do_something.
  do_this( ).
  then_that( ).
ENDMETHOD.

No hay razón para tener el mal hábito de separar tu código con líneaas en blanco

" anti-pattern
METHOD do_something.

  do_this( ).

  then_that( ).

ENDMETHOD.

Las líneas en blanco solo hacen sentido si tienes una sentencia que usa varias líneas

METHOD do_something.

  do_this( ).

  then_that(
    EXPORTING
      variable = 'A'
    IMPORTING
      result   = result ).

ENDMETHOD.

Alinea asignaciones al mismo objeto, pero no a objetos diferentes

Clean ABAP > Contenido > Formato > Esta sección

Para resaltar que estas cosas van juntas

structure-type = 'A'.
structure-id   = '4711'.

o aún mejor

structure = VALUE #( type = 'A'
                     id   = '4711' ).

Pero no alinees cosas que no tienen nada que ver una con la otra:

customizing_reader = fra_cust_obj_model_reader=>s_get_instance( ).
hdb_access = fra_hdbr_access=>s_get_instance( ).

Lee más en Capítulo 5: Formato: Alineación horizontal de [Código Limpio por Robert C. Martin].

Cierra paréntesis en la última línea de código

Clean ABAP > Contenido > Formato > Esta sección

modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

en lugar del innecesariamente largo

" anti-pattern
modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields
).

Mantén llamadas de un solo parámetro en una línea

Clean ABAP > Contenido > Formato > Esta sección

DATA(unique_list) = remove_duplicates( list ).
remove_duplicates( CHANGING list = list ).

en lugar del innecesariamente largo

" anti-pattern
DATA(unique_list) = remove_duplicates(
                           list ).
DATA(unique_list) = remove_duplicates(
                         CHANGING
                           list = list ).

Mantén los parámetros detrás de la llamada

Clean ABAP > Contenido > Formato > Esta sección

DATA(sum) = add_two_numbers( value_1 = 5
                             value_2 = 6 ).

Cuando esto haga las líneas muy largas, puedes hacer un salto de línea con los parámetros a la siguiente línea:

DATA(sum) = add_two_numbers(
                value_1 = round_up( input DIV 7 ) * 42 + round_down( 19 * step_size )
                value_2 = VALUE #( ( `Calculation failed with a very weird result` ) ) ).

Si haces un salto de línea, indenta parámetros debajo de la llamada

Clean ABAP > Contenido > Formato > Esta sección

DATA(sum) = add_two_numbers(
                value_1 = 5
                value_2 = 6 ).

Alinear los parámetros en cualquier otra parte provoca que sea difícil identificar a qué pertenecen:

DATA(sum) = add_two_numbers(
    value_1 = 5
    value_2 = 6 ).

Sin embargo, este es el mejor patrón si quieres evitar que el formato se descomponga por el cambio de la longitud de un nombre.

Usa saltos de línea para múltiples parámetros

Clean ABAP > Contenido > Formato > Esta sección

DATA(sum) = add_two_numbers( value_1 = 5
                             value_2 = 6 ).

Sí, esto desperdicia espacio. Sin embargo, de otra manera, es difícil identificar dónde termina un parámetro y dónde empieza el siguiente.

" anti-pattern
DATA(sum) = add_two_numbers( value_1 = 5 value_2 = 6 ).

Alinea los parámetros

Clean ABAP > Contenido > Formato > Esta sección

modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

No alinear los parámetros hace difícil ver donde termina el parámetro y comienza su valor:

" anti-pattern
modify->update( node = if_fra_alert_c=>node-item
                key = item->key
                data = item
                changed_fields = changed_fields ).

Por otro lado, este es el mejor patrón si quieres evitar que el formato se pierda por un cambio de longitud en un nombre.

Usa un salto de línea si la llamada a un método se vuelve muy extensa

Clean ABAP > Contenido > Formato > Esta sección

DATA(some_super_long_param_name) =
  if_some_annoying_interface~add_two_numbers_in_a_long_name(
      value_1 = 5
      value_2 = 6 ).

Usa indentado apropiado

Clean ABAP > Contenido > Formato > Esta sección

Indenta los parámetros de palabras clave con 2 espacios y los parámetros con 4 espacios:

DATA(sum) = add_two_numbers(
              EXPORTING
                value_1 = 5
                value_2 = 6
              CHANGING
                errors  = errors ).

Si no tienes palabras clave, indenta con 4 espacios. If you have no keywords, indent the parameters by 4 spaces.

DATA(sum) = add_two_numbers(
                value_1 = 5
                value_2 = 6 ).

Usa la tecla de Tab para indentar. Está bien si esto agrega un espacio más del necesario. (Esto pasa si la parte de la izquierda en DATA(sum) = tiene un número impar de caracteres).

Indenta declaraciones in-line como llamadas a métodos

Clean ABAP > Contenido > Formato > Esta sección

Indenta las declaraciones in-line con VALUE o NEW como si fueran llamadas a métodos:

DATA(result) = merge_structures( a = VALUE #( field_1 = 'X'
                                              field_2 = 'A' )
                                 b = NEW /clean/structure_type( field_3 = 'C'
                                                                field_4 = 'D' ) ).

No alinees los TYPE

Clean ABAP > Contenido > Formato > Esta sección

DATA name TYPE seoclsname.
DATA reader TYPE REF TO /clean/reader.

Una variable y su tipo deben estar juntos y por lo tanto visualmente agrupados uno próximo al otro. Alinear los TYPE aleja la atención de eso e indica que las variables forman un grupo vertical y sus tipos otro. La alineación también provoca overhead innecesario de edición, requiriendo que ajustes todas las indentaciones cuando el nombre de la variable más larga cambia.

" anti-pattern
DATA name   TYPE seoclsname.
DATA reader TYPE REF TO /clean/reader.

Testing

Clean ABAP > Contenido > Esta sección

Principios

Clean ABAP > Contenido > Testing > Esta sección

Escribe código que se pueda probar

Clean ABAP > Contenido > Testing > Principios > Esta sección

Escribe todo tu código de manera que te permita probarlo automáticamente.

Si esto requiere que lo refactorices, hazlo. Haz eso primero, antes de comenzar a agregar otras funcionalidades.

Si agregas funcionalidades a código legacy que está muy mal estructurado para ser probado, refactorízalo por lo menos hasta que puedas probar tu funcionalidad agregada.

Permite que otros hagan mock de tu código

Clean ABAP > Contenido > Testing > Principios > Esta sección

Si escribes tu propio código para ser consumido por otros, permite que escriban pruebas unitarias para su propio código. Esto se puede hacer, por ejemplo, agregando interfaces en todas las porciones de código públicas, proveyendo dobles de prueba que facilitan las pruebas de integración o aplicando inversión de dependencias para habilitarlos a que sustituyan la configuración productiva por una de prueba.

Reglas de legibilidad

Clean ABAP > Contenido > Testing > Principios > Esta sección

Haz tu prueba aún más legible que tu código productivo. Puedes manejar código productivo malo con buenas pruebas, pero si no le entiendes ni a las pruebas, estás perdido chavo.

Mantén tu código de prueba tan simple y estúpido que lo seguirás entendiendo dentro de un año.

Apégate a los estándares y patrones, para permitir que tus compañeros se adapten rápidamente al código.

No hagas copias ni escribas reportes de prueba

Clean ABAP > Contenido > Testing > Principios > Esta sección

No comiences a trabajar en un requerimiento haciendo una copia en $TMP de un objeto de desarrollo. Otros no notarán estos objetos y no sabrán el estatus de tu avance. Probablemente desperdiciarás mucho tiempo haciendo que funcione la copia en primer lugar. Probablemente olvides borrar la copia después, dejando basura y dependencias en el sistema. (¿Crees que no pasa? Ve a tu sistema de desarrollo y revisa tu $TMP en este momento)

No comiences escribiendo un reporte de prueba que llama algo de una manera específica y que esa sea tu herramienta para verificar que las cosas siguen funcionando. Estas son las pruebas del pobre: repitiendo un reporte de prueba a mano y verificando visualmente que todo siga bien.

Toma el siguiente paso y automatiza este reporte en una prueba unitaria, con una aserción automática que te diga si tu código aún está bien. Te vas a salvar a ti mismo del esfuerzo de tener que escribir las pruebas unitarias después. Además, salvarás mucho tiempo de las repeticiones manuales y evitarás aburrirte y cansarte de hacerlo.

Prueba componentes públicos, no los privados

Clean ABAP > Contenido > Testing > Principios > Esta sección

Las partes públicas de las clases, especialmente las interfaces que implementan, son relativamente estables y es poco probable que cambien. Tus pruebas unitarias solo deberían validar los componentes públicos para que sean robustas y minimicen el esfuerzo que tienes que hacer cuando refactorices la clase. Los componentes internos PRIVATE y PROTECTED pueden llegar a cambiar muy rápido a través de refactorización, lo cual provocaría que tus pruebas dejen de funcionar sin necesidad.

Una necesidad urgente de probar métodos PRIVATE o PROTECTED puede ser una advertencia temprana de fallas en tu diseño. Pregúntate:

  • ¿Escondiste accidentalmente un concepto en tu clase que quiere salir en su propia clase, con su propio set de pruebas?

  • ¿Olvidaste separar la lógica de dominio del código del glue code? Por ejemplo, implementar la lógica de dominio directamente en la clase que está conectada a BOPF como una acción, determinación o validación o que fue generada por SAP Gateway como un proveedor de datos *_DPC_EXT, puede no ser la mejor idea.

  • ¿Tus interfaces son tan complicadas y requieren tantos datos irrelevantes que no se le puede crear un mock fácilmente?

No te obsesiones con la cobertura del código

Clean ABAP > Contenido > Testing > Principios > Esta sección

La cobertura del código está ahí para ayudarte a encontrar código que olvidaste probar, no para cumplir un KPI aleatorio.

No inventes pruebas con o sin aserciones dummy solo para llegar a una meta de cobertura de código.

Es mejor dejar componentes sin pruebas para que sea transparente que no se pueden refactorizar adecuadamente. Hay casos - como IFs en el constructor para insertar dobles de prueba - que hacen impráctico llegar al 100%. Las buenas pruebas tienden a cubrir la misma sentencia en múltiples ocasiones, para diferentes bloques y condiciones. De hecho, tendrán una cobertura imaginaria > 100%.

Clases de prueba

Clean ABAP > Contenido > Testing > Esta sección

Llama las clases locales de prueba de acuerdo a su objetivo

Clean ABAP > Contenido > Testing > Clases de prueba > Esta sección

Nombra las clases de prueba por el "cuando" de la historia

CLASS ltc_<public method name> DEFINITION FOR TESTING ... ."

o por el "dado" de la historia

CLASS ltc_<common setup semantics> DEFINITION FOR TESTING ... .
" anti-patterns
CLASS ltc_fra_online_detection_api DEFINITION FOR TESTING ... . " We know that's the class under test - why repeat it?
CLASS ltc_test DEFINITION FOR TESTING ....                      " Of course it's a test, what else should it be?

Coloca tus pruebas en clases locales

Clean ABAP > Contenido > Testing > Clases de prueba > Esta sección

Coloca tus pruebas unitarias en el include de pruebas locales de la clase bajo prueba. Esto asegura que las personas encuentren estas pruebas cuando refactoricen la clase y permite que corran todos las pruebas asociadas con un solo atajo de teclado, como está descrito en Cómo ejecutar clases de prueba

Coloca las pruebas de componente, integración y sistema en el include de prueba local de una clase global separada. No se relacionan directamente a una sola clase bajo prueba y por lo tanto no deberían ser colocadas arbitrariamente en una de las clases involucradas, sino en una separada.

Marca esta clase global de prueba como FOR TESTING y ABSTRACT para evitar que sea accidentalmente utilizada en código de producción. Poner pruebas en otras clases tiene el peligro de que las personas no las vean y olviden correr las pruebas antes de refactorizar.

Por lo tanto es beneficioso usar test relations para documentar cuáles objetos esán siendo probados por la prueba.

Con el ejemplo de abajo de la clase de prueba hiring_test se podría ejecutar al estar en la clase recruiting o candidate via el atajo Shift + Ctrl + F12 (Windows) o Cmd + Shift + F12 (macOS).

"! @testing recruiting
"! @testing candidate
class hiring_test definition
  for testing risk level dangerous duration medium
  abstract.
  ...
endclass.

Coloca métodos de ayuda en clases de ayuda

Clean ABAP > Contenido > Testing > Clases de prueba > Esta sección

Coloca métodos auxiliares usados por varias clases de prueba en una clase auxiliar. Haz los métodos auxiliares disponibles mediante herencia o delegación.

" inheritance example

CLASS lth_unit_tests DEFINITION ABSTRACT.

  PROTECTED SECTION.
    CLASS-METHODS assert_activity_entity
      IMPORTING
        actual_activity_entity TYPE REF TO zcl_activity_entity
        expected_activity_entity TYPE REF TO zcl_activity_entity.
    ...
ENDCLASS.

CLASS lth_unit_tests IMPLEMENTATION.

  METHOD assert_activity_entity.
    ...
  ENDMETHOD.

ENDCLASS.

CLASS ltc_unit_tests DEFINITION INHERITING FROM lth_unit_tests FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.
  ...
ENDCLASS.

Cómo ejecutar clases de prueba

Clean ABAP > Contenido > Testing > Clases de prueba > Esta sección

En ABAP Development Tools, presiona Ctrl + Shift + F10 para correr todas las pruebas en una clase. Presiona Ctrl + Shift + F11 para incluir mediciones de cobertura. Presiona Ctrl + Shift + F12 para también correr pruebas en otras clases que están mantenidas como relaciones.

En macOS, usa Cmd en lugar de Ctrl.

Código bajo prueba

Clean ABAP > Contenido > Testing > Esta sección

Nombra el código bajo prueba con sentido, o usa CUT como default

Clean ABAP > Contenido > Testing > Código Bajo Prueba > Esta sección

Nombra la variable que representa el código bajo prueba un nombre con un significado adecuado:

DATA blog_post TYPE REF TO ...

No solo repitas el nombre de la clase con todos sus prefijos que no aportan valor:

" anti-pattern
DATA clean_fra_blog_post TYPE REF TO ...

Si tienes diferentes setups de prueba, puede ser de ayuda describir el estado variante del objeto:

DATA empty_blog_post TYPE REF TO ...
DATA simple_blog_post TYPE REF TO ...
DATA very_long_blog_post TYPE REF TO ...

Si tienes problemas encontrando un nombre adecuado, usa cut como default. La abreviación proviene de code under test.

DATA cut TYPE REF TO ...

En pruebas confusas con código no limpio, llamar la variable cut puede temporalmente ayudar al lector a identificar qué es lo que se está probando. Sin embargo, limpiar las pruebas es la verdadera solución a largo plazo.

Prueba sobre interfaces, no implementaciones

Clean ABAP > Contenido > Testing > Código Bajo Prueba > Esta sección

Una consecuencia práctica de Prueba componentes públicos, no los privados, el tipo de tu código bajo prueba debe ser una interfaz.

DATA code_under_test TYPE REF TO some_interface.

en lugar de una clase

" anti-pattern
DATA code_under_test TYPE REF TO some_class.

Extrae la llamada al código bajo prueba a su propio método

Clean ABAP > Contenido > Testing > Código Bajo Prueba > Esta sección

Si el método a ser probado requiere muchos parámetros o preparación de datos, puede ayudar extraer la llamada a un método auxiliar que ponga por default los parámetros no relevantes para la prueba:

METHODS map_xml_to_itab
  IMPORTING
    xml_string TYPE string
    config     TYPE /clean/xml2itab_config DEFAULT default_config
    format     TYPE /clean/xml2itab_format DEFAULT default_format.

METHOD map_xml_to_itab.
  result = cut->map_xml_to_itab( xml_string = xml_string
                                 config     = config
                                 format     = format ).
ENDMETHOD.

DATA(itab) = map_xml_to_itab( '<xml></xml>' ).

Llamar al método original directamente puede ensuciar tu prueba con muchos detalles sin relevancia:

" anti-pattern
DATA(itab) = cut->map_xml_to_itab( xml_string = '<xml></xml>'
                                   config     = VALUE #( 'some meaningless stuff' )
                                   format     = VALUE #( 'more meaningless stuff' ) ).

Inyección

Clean ABAP > Contenido > Testing > Esta sección

Usa inversión de dependencias para inyectar dobles de prueba

Clean ABAP > Contenido > Testing > Inyección > Esta sección

La inversión de dependencias significa que le entregas todas tus dependencias al constructor_

METHODS constructor
  IMPORTING
    customizing_reader TYPE REF TO if_fra_cust_obj_model_reader.

METHOD constructor.
  me->customizing_reader = customizing_reader.
ENDMETHOD.

No uses inyección por setter. Permite que el código productivo sea usado en maneras que no se pretende:

" anti-pattern
METHODS set_customizing_reader
  IMPORTING
    customizing_reader TYPE REF TO if_fra_cust_obj_model_reader.

METHOD do_something.
  object->set_customizing_reader( a ).
  object->set_customizing_reader( b ). " would you expect that somebody does this?
ENDMETHOD.

No uses inyección mediante FRIENDS. Esto inicializará las dependencias productivas antes de que sean reemplazadas, probablemente provocando consecuencias no esperadas. Don't use FRIENDS injection. Además dejará de funcionar tan pronto renombres los componentes internos y se salta las inicializaciones del constructor.

" anti-pattern
METHOD setup.
  cut = NEW fra_my_class( ). " <- builds a productive customizing_reader first - what will it break with that?
  cut->customizing_reader ?= cl_abap_testdouble=>create( 'if_fra_cust_obj_model_reader' ).
ENDMETHOD.

METHOD constructor.
  customizing_reader = fra_cust_obj_model_reader=>s_get_instance( ).
  customizing_reader->fill_buffer( ). " <- won't be called on your test double, so no chance to test this
ENDMETHOD.

Considera usar la herramienta de ABAP test double

Clean ABAP > Contenido > Testing > Inyección > Esta sección

DATA(customizing_reader) = CAST /clean/customizing_reader( cl_abap_testdouble=>create( '/clean/default_custom_reader' ) ).
cl_abap_testdouble=>configure_call( customizing_reader )->returning( sub_claim_customizing ).
customizing_reader->read( 'SOME_ID' ).

Más corto y sencillo que entender dobles de prueba personalizados:

" anti-pattern
CLASS /dirty/default_custom_reader DEFINITION FOR TESTING CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES /dirty/customizing_reader.
    DATA customizing TYPE /dirty/customizing_table.
ENDCLASS.

CLASS /dirty/default_custom_reader IMPLEMENTATION.
  METHOD /dirty/customizing_reader~read.
    result = customizing.
  ENDMETHOD.
ENDCLASS.

METHOD test_something.
  DATA(customizing_reader) = NEW /dirty/customizing_reader( ).
  customizing_reader->customizing = sub_claim_customizing.
ENDMETHOD.

Explota el uso de las herramientas de prueba

Clean ABAP > Contenido > Testing > Inyección > Esta sección

En general, un estilo de programación limpio te dejará hacer mucho del trabajo con pruebas unitarias ABAP estándar y dobles de prueba. Sin embargo, hay herramientas que te permitirán atacar casos más complejos:

  • Usa el servicio CL_OSQL_REPLACE para probar sentencias complejas de OpenSQL, redireccionándolas a una fuente de datos de prueba sin que las operaciones interfieran con el resto del sistema.

  • Usa la librería de prueba de CDS para probar tus vistas CDS.

Usa test seams como una solución temporal

Clean ABAP > Contenido > Testing > Inyección > Esta sección

Si todas las otras técnicas fallan, o cuando estés en las aguas peligrosas del código legacy, utiliza test seams para que sea posible probar.

Aunque puede parecer cómodo de usar a primera vista, los test seams son invasivos y tienden a enredarse con dependencias privadas, de tal manera que es difícil mantenerlos vivos y estables en el largo plazo.

Por lo tanto recomendamos abstenerse de usar test seams, usándolos solo como una solución temporal para permitir refactorizar el código en una forma más apta para probarse.

Usa LOCAL FRIENDS para acceder al constructor inversor de dependencias

Clean ABAP > Contenido > Testing > Inyección > Esta sección

CLASS /clean/unit_tests DEFINITION.
  PRIVATE SECTION.
    DATA cut TYPE REF TO /clean/interface_under_test.
    METHODS setup.
ENDCLASS.

CLASS /clean/class_under_test DEFINITION LOCAL FRIENDS unit_tests.

CLASS unit_tests IMPLEMENTATION.
  METHOD setup.
    DATA(mock) = cl_abap_testdouble=>create( '/clean/some_mock' ).
    " /clean/class_under_test is CREATE PRIVATE
     " so this only works because of the LOCAL FRIENDS
    cut = NEW /clean/class_under_test( mock ).
  ENDMETHOD.
ENDCLASS.

No uses LOCAL FRIENDS para invadir el código bajo prueba

Clean ABAP > Contenido > Testing > Inyección > Esta sección

Las pruebas unitarias que acceden componentes PRIVATE y PROTECTED para insertar datos con un mock son frágiles: dejan de funcionar tan pronto la estructura interna del código bajo prueba cambia.

" anti-pattern
CLASS /dirty/class_under_test DEFINITION LOCAL FRIENDS unit_tests.
CLASS unit_tests IMPLEMENTATION.
  METHOD returns_right_result.
    cut->some_private_member = 'AUNIT_DUMMY'.
  ENDMETHOD.
ENDCLASS.

No cambies el código productivo para poder probarlo

Clean ABAP > Contenido > Testing > Inyección > Esta sección

" anti-pattern
IF me->in_test_mode = abap_true.

No uses herencia para hacer mock a métodos

Clean ABAP > Contenido > Testing > Inyección > Esta sección

No uses herencia para sobre-escribir métodos y crearles un mock en tus pruebas. Aunque esto funciona, es muy frágil ya que las pruebas dejan de funcionar al refactorizar el código. Esto también permite que consumidores productivos de la clase puedan heredar de ella que puede tomarte desprevenido si no se planeó específicamente para ello

" anti-pattern
CLASS unit_tests DEFINITION INHERITING FROM /dirty/real_class FOR TESTING [...].
  PROTECTED SECTION.
    METHODS needs_to_be_mocked REDEFINITION.

Para lograr usar código legacy para pruebas, utiliza test seams. Son igual de frágiles, pero aún así una manera más limpia porque por lo menos no cambian el comportamiento productivo de la clase, como pasaría al habilitar herencia quitando el FINAL o cambiando el alcance de un método de PRIVATE a PROTECTED.

Al escribir nuevo código, toma este punto de posibilidad de probar la clase directamente como parte de su diseño y encuentra una manera diferente y mejor. Las mejores prácticas comunes incluyen usar las herramientas de pruebas y extraer el método problema a una clase separada con su propia interfaz.

Una variante más específica de No cambies el código productivo para poder probarlo.

No hagas mock sin necesidad

Clean ABAP > Contenido > Testing > Inyección > Esta sección

cut = NEW /clean/class_under_test( db_reader = db_reader
                                   config    = VALUE #( )
                                   writer    = VALUE #( ) ).

Define tus "dados" tan precisamente como sea posible: no asignes información que tu prueba no necesita y no le hagas mock a objetos que nunca se llaman. Estas coss distraen al lector de lo que realmente está pasando.

" anti-pattern
cut = NEW /dirty/class_under_test( db_reader = db_reader
                                   config    = config
                                   writer    = writer ).

También hay casos donde no es necesario crear un mock para nada - este es usualmente el caso con estructuras de datos y contenedores de datos. Por ejemplo, tus casos de prueba pueden funcionar bien con la versión productiva de transient_log porque solo almacena datos sin efectos secundarios.

No crees librerías para pruebas

Clean ABAP > Contenido > Testing > Inyección > Esta sección

Las pruebas unitarias - en contraste con las pruebas de integración - deberían de ser una caja negra con datos que entran y datos que salen, con todos los datos de prueba siendo definidos a medida que se requieren.

cl_abap_testdouble=>configure_call( test_double )->returning( data ).

No comiences a construir librerías que distingan "test case IDs" para decidir qué datos se van a usar. El código resultante será tan largo y enredado que no te será posible mantener estas pruebas vivas en el largo plazo.

" anti-pattern

test_double->set_test_case( 1 ).

CASE me->test_case.
  WHEN 1.
  WHEN 2.
ENDCASE.

Métodos de prueba

Clean ABAP > Contenido > Testing > Esta sección

Nomenclatura para métodos de prueba: considera el supuesto y el resultado esperado

Clean ABAP > Contenido > Testing > Métodos de prueba > Esta sección

Nombres adecuados para métodos de prueba reflejan el "dado" y el "entonces"

METHOD reads_existing_entry.
METHOD throws_on_invalid_key.
METHOD detects_invalid_input.

Nombres poco útiles reflejan el "cuando", repiten hechos sin sentido o son crípticos:

" anti-patterns

" What's expected, success or failure?
METHOD get_conversion_exits.

" It's a test method, what else should it do but "test"?
METHOD test_loop.

" So it's parameterized, but what is its aim?
METHOD parameterized_test.

" What's "_wo_w" supposed to mean and will you still remember that in a year from now?
METHOD get_attributes_wo_w.

Ya que ABAP solo permite 30 caracteres en nombres de métodos, es justo agregar un comentario para explicar si el nombre es muy corto para transmitir el significado. ABAP Doc o la primera línea en el método de prueba puede ser una elección apropiada para el comentario.

Teniendo muchos métodos de prueba que tienen nombres muy largos puede ser un indicador de que necesitas separar tu clase de prueba en varias y expresar las diferencias en los "dados" de los nombres de las clases.

Having lots of test methods whose names are too long may be an indicator that you should split your single test class into several ones and express the differences in the givens in the class's names.

Usa dado-cuando-entonces

Clean ABAP > Contenido > Testing > Métodos de prueba > Esta sección

Organiza tu código de prueba junto con el paradigma de dado-cuando-entonces: Primero, inicializa las cosas en una sección donde se dan supuestos ("dado"), después, llama el método a probar ("cuando"), y finalmente valida el resultado ("entonces").

Si la sección de "dado" o "entonces" son tan extensas qeu no puedes separar visualmente las tres secciones, refactoriza en sub-métodos. Usar líneas blancas o comentarios como separadores puede parecer buena idea a primera vista, pero no reduce la contaminación visual. Aún así, son de ayuda para el lector y el escritor de pruebas novato para separar las secciones.

"Cuando" es exactamente una llamada

Clean ABAP > Contenido > Testing > Métodos de prueba > Esta sección

Asegúrate que la sección de "cuando" de tu código de prueba contenga exactamente una llamada a la clase bajo prueba:

METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
ENDMETHOD.

Llamar múltiples cosas indica que el método no tiene un enfoque claro y prueba demasiado. Esto hace más difícil encontrar la causa cuando la prueba falla: ¿fue la primera, segunda o tercera llamada que causó el error? También confunde al lector porque no está seguro de cuál es la funcionalidad exacta que está siendo probada

No uses el método TEARDOWN a menos que realmente lo necesites

Clean ABAP > Contenido > Testing > Métodos de prueba > Esta sección

Los métodos teardown son usualmente necesarios para limpiar entradas en base de datos u otros recursos en pruebas de integración.

Reiniciar miembros de la clase de prueba, especialmente cut y los dobles de prueba, es superfluo; son sobre-escritos por el método setup antes de que se ejecute el siguiente método de prueba.

Datos de prueba

Clean ABAP > Contenido > Testing > Esta sección

Haz que sea fácil detectar la intención

Clean ABAP > Contenido > Testing > Datos de prueba > Esta sección

En pruebas unitarias, quieres poder identificar rápidamente qué datos y dobles son importantes y cuales solo están ahí para que el código no falle. Permite esto dándole valores que no tienen un significado específico a los datos de prueba, por ejemplo:

DATA(alert_id) = '42'.                             " well-known meaningless numbers
DATA(detection_object_type) = '?=/"&'.             " 'keyboard accidents'
CONSTANTS some_random_number TYPE i VALUE 782346.  " revealing variable names

No engañes al lector haciéndole creer que algo se conecta a objetos reales o customizing real, si no lo hace:

" anti-pattern
DATA(alert_id) = '00000001223678871'.        " this alert really exists
DATA(detection_object_type) = 'FRA_SCLAIM'.  " this detection object type, too
CONSTANTS memory_limit TYPE i VALUE 4096.    " this number looks carefully chosen

Haz que sea fácil detectar las diferencias

Clean ABAP > Contenido > Testing > Datos de prueba > Esta sección

exp_parameter_in = VALUE #( ( parameter_name = '45678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789END1' )
                            ( parameter_name = '45678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789END2' ) ).

No forces al lector a comparar strings extensos para detectar diferencias mínimas.

Usa constantes para describir el objetivo y la importancia de los datos de prueba

Clean ABAP > Contenido > Testing > Datos de prueba > Esta sección

CONSTANTS some_nonsense_key TYPE char8 VALUE 'ABCDEFGH'.

METHOD throws_on_invalid_entry.
  TRY.
      " when
      cut->read_entry( some_nonsense_key ).
      cl_abap_unit_assert=>fail( ).
    CATCH /clean/customizing_reader_error.
      " then
  ENDTRY.
ENDMETHOD.

Aserciones

Clean ABAP > Contenido > Testing > Esta sección

Usa pocas aserciones, enfocadas

Clean ABAP > Contenido > Testing > Aserciones > Esta sección

Haz una aserción únicamente sobre lo que se trata el método de prueba y con un número pequeño de aserciones.

METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
ENDMETHOD.

Hacer muchas aserciones es un indicador de que el método no tiene un enfoque claro. Esto acopla código productivo y de prueba en muchos lugares: cambiar una funcionalidad requerirá reescribir un número largo de pruebas, aunque realmente no tengan que ver con la funcionalidad. También confunde al lector con una variedad larga de aserciones, escondiendo la aserción importante.

" anti-pattern
METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
  cl_abap_unit_assert=>assert_not_initial( log->get_messages( ) ).
  cl_abap_unit_assert=>assert_equals( act = sy-langu
                                      exp = 'E' ).
ENDMETHOD.

Usa el tipo de aserción correcto

Clean ABAP > Contenido > Testing > Aserciones > Esta sección

cl_abap_unit_assert=>assert_equals( act = table
                                    exp = test_data ).

Las aserciones usualmente hacen más de lo que parece, por ejemplo assert_equals incluye comparación de tipos y provee descripciones precisas si los valores son diferentes. Usar el tipo incorrecto te forzará inmediatamente a usar el debugger en lugar de permitirte ver que está mal desde el mensaje de error.

" anti-pattern
cl_abap_unit_assert=>assert_true( xsdbool( act = exp ) ).

Haz aserciones sobre el contenido, no la cantidad

Clean ABAP > Contenido > Testing > Aserciones > Esta sección

assert_contains_exactly( actual   = table
                         expected = VALUE string_table( ( `ABC` ) ( `DEF` ) ( `GHI` ) ) ).

No escribas aserciones con números mágicos si puedes expresar el contenido actual que esperas. Los números pueden variar, aunque las expectativas se cumplan. A la inversa, los números pueden cuadrar aunque el contenido sea completamente inesperado.

" anti-pattern
assert_equals( act = lines( log_messages )
               exp = 3 ).

Haz aserciones sobre la calidad, no el contenido

Clean ABAP > Contenido > Testing > Aserciones > Esta sección

Si estás interesado en una meta característica del resultado, pero no en el contenido real, expresa esto con una aserción adecuada:

assert_all_lines_shorter_than( actual_lines        = table
                               expected_max_length = 80 ).

Hacerle una aserción al contenido esconde lo que realmente quieres probar. También es frágil, porque refactorizar pueede producir un resultado diferente pero perfectamente aceptable, aunque provoque que tus pruebas unitarias precisas dejen de funcionar.

" anti-pattern
assert_equals( act = table
               exp = VALUE string_table( ( `ABC` ) ( `DEF` ) ( `GHI` ) ) ).

Usa FAIL para evaluar excepciones esperadas

Clean ABAP > Contenido > Testing > Aserciones > Esta sección

METHOD throws_on_empty_input.
  TRY.
      " when
      cut->do_something( '' ).
      cl_abap_unit_assert=>fail( ).
    CATCH /clean/some_exception.
      " then
  ENDTRY.
ENDMETHOD.

Propaga excepciones inesperadas en lugar de atraparlas y enviar error

Clean ABAP > Contenido > Testing > Aserciones > Esta sección

METHODS reads_entry FOR TESTING RAISING /clean/some_exception.

METHOD reads_entry.
  "when
  DATA(entry) = cut->read_something( ).
  "then
  cl_abap_unit_assert=>assert_not_initial( entry ).
ENDMETHOD.

Tu código de prueba se mantiene enfocado en el happy path y por lo tanto es mucho más fácil de leer y entender, comparado con:

" anti-pattern
METHOD reads_entry.
  TRY.
      DATA(entry) = cut->read_something( ).
    CATCH /clean/some_exception INTO DATA(unexpected_exception).
      cl_abap_unit_assert=>fail( unexpected_exception->get_text( ) ).
  ENDTRY.
  cl_abap_unit_assert=>assert_not_initial( entry ).
ENDMETHOD.

Escribe aserciones a la medida para reducir el código y evitar duplicados

Clean ABAP > Contenido > Testing > Aserciones > Esta sección

METHODS assert_contains
  IMPORTING
    actual_entries TYPE STANDARD TABLE OF entries_tab
    expected_key   TYPE key_structure.

METHOD assert_contains.
  TRY.
      actual_entries[ key = expected_key ].
    CATCH cx_sy_itab_line_not_found.
      cl_abap_unit_assert=>fail( |Couldn't find the key { expected_key }| ).
  ENDTRY.
ENDMETHOD.

En lugar de copiar y pegar una y otra vez.