Refactorización de una API de cliente para evitar código duplicado y paso poco claro de parámetros

8

Necesito desarrollar una API, las funciones de la API son solicitudes que llaman al servicio expuesto por un servidor.

Inicialmente, la API funcionó así:

class Server:
    def firstRequest(self, arg1, arg2):
        # block of code A
        async = Async()
        async.callFirstRequest(arg1, arg2)
        # block of code B

    def secondRequest(self, argA, argB, argC):
        # block of code A (identical to that of firstRequest)
        async = Async()
        async.callSecondRequest(argA, argB, argC)
        # block of code B (identical to that of firstRequest)

class Async:
    def callFirstRequest(self, arg1, arg2):
        doFirstRequest(arg1, arg2)

    # run the real request and wait for the answer
    def doFirstRequest(self, arg1, arg2):
        response = client.firstRequest(arg1, arg2)

    def callSecondRequest(self, argA, argB, argC):
        doSecondRequest(argA, argB, argC)

    # run the real request and wait for the answer
    def doSecondRequest(self, argA, argB, argC):
        response = client.secondRequest(argA, argB, argC)

server = Server()
server.firstRequest(arg1=1, arg2=2)
server.secondRequest(argA='A', argB='B', argC='C')

Había mucho código duplicado y no me gustó la forma en que pasó los argumentos de la solicitud. Debido a que hay muchos argumentos, quería extraerlos de la solicitud y hacer algo más paramétrico.

Entonces refactoré de esta manera:

# using a strategy pattern I was able to remove the duplication of code A and code B
# now send() receive and invoke the request I wanna send
class Server:
    def send(self, sendRequest):
        # block of code A
        asynch = Async()
        sendRequest(asynch)
        # block of code B

# Request contains all the requests and a list of the arguments used (requestInfo)
class Request:
    # number and name of the arguments are not the same for all the requests
    # this function take care of this and store the arguments in RequestInfo for later use
    def setRequestInfo(self, **kwargs):
        if kwargs is not None:
            for key, value in kwargs.iteritems():
                self.requestInfo[key] = value

    def firstRequest(async)
        async.doFirstRequest(self.requestInfo)

    def secondRequest(async)
        async.doSecondRequest(self.requestInfo)

# Async run the real request and wait for the answer
class Async:
    def doFirstRequest(requestInfo):
        response = client.firstRequest(requestInfo['arg1'], requestInfo['arg2'])

    def doSecondRequest(requestInfo)
        response = client.secondRequest(requestInfo['argA'], requestInfo['argB'], requestInfo['argC'])  


server = Server()
request = Request()

request.setRequestInfo(arg1=1, arg2=2) # set of the arguments needed for the request
server.send(request.firstRequest)

request.setRequestInfo(argA='A', argB='B', argC='C')
server.send(request.secondRequest)

El patrón de estrategia funcionó, la duplicación se elimina. Independientemente de esto, me temo que tengo cosas complicadas, especialmente en lo que respecta a los argumentos, no me gusta la forma en que los manejo, porque cuando miro el código no parece fácil y claro.

Así que quería saber si hay un patrón o una forma mejor y más clara de lidiar con este tipo de código API del lado del cliente.

k4ppa
fuente
1
¿Has considerado preguntar sobre esto en el intercambio de revisión de código? Los usuarios allí están especializados en cosas como esta.
Nzall
@NateKerkhofs Sí, lo he considerado, pero luego elijo Programadores porque este es el tablero donde hacer preguntas sobre diseño y arquitectura como leí en la sección de ayuda . En Code Review ni siquiera hay una etiqueta para refactorizar.
k4ppa
2
Esto se debe a que todo el objetivo de CR es refactorizar el código. Básicamente está implícito que todas las preguntas allí son sobre la refactorización del código. Su código ya debería funcionar, pero aparte de eso, las respuestas suponen que desea que su código sea revisado por errores y refactorización.
Nzall
2
@NateKerkhofs - CR tiende a tener un enfoque muy limitado con respecto a una revisión de diseño. Como el OP solicita orientación sobre la arquitectura de la solución, creo que es sobre el tema de este sitio.

Respuestas:

1

Reconsideraría usar un diccionario (hash, mapa, lo que sea que su idioma llame un conjunto de pares clave / valor) para los argumentos. Hacerlo de esa manera hace imposible que el compilador verifique si la persona que llama ha incluido todos los valores necesarios. A los desarrolladores les resulta difícil determinar si tienen todos los argumentos necesarios. Hace que sea fácil incluir accidentalmente algo que no necesitaba y olvidar algo que sí necesitaba. Y termina teniendo que poner todos los valores en el diccionario al llamar, y tener que verificar el diccionario en cada función para extraer todos los argumentos, lo que aumenta la sobrecarga. El uso de algún tipo de estructura especializada puede reducir el número de argumentos sin reducir la capacidad de los compiladores para verificarlos y la capacidad de los desarrolladores para ver claramente lo que se necesita.

usuario1118321
fuente
Solo como complemento, si le preocupa asegurarse de que se pasan todos los parámetros para una solicitud determinada, puede usar las clases o interfaces abstractas (no recuerde si eso existe en Python de la misma manera que C # o Java) , pero eso podría volverse excesivo dependiendo del alcance actual. El principal beneficio sería tener definiciones claras por solicitud.
eparham7861
0

Creo que la API de su servidor debería tener tantas entradas como solicitudes requeridas. Por lo tanto, cualquier desarrollador podrá leer la API fácilmente ( consulte el enrutamiento de lask como ejemplo ).

Para evitar la duplicación en el código, puede usar métodos internos

juanmiguelRua
fuente
0

API, arquitectura y patrón tienen que ver con la comunicación y la intención. Su segunda versión me parece bastante simple y parece algo extensible también, pero mi opinión (o incluso la suya) no es lo que importa aquí.

Obtenga comentarios

Mire su software de afuera hacia adentro (no de adentro hacia afuera). Si tiene un sitio web, comience desde allí. Espere encontrar fácilmente una y solo una forma obvia de hacer lo que se espera que haga su software. Encuentre a alguien en el pasillo y solicite comentarios sobre la usabilidad.

Identificar el objeto comercial principal

Dado que la programación de software siempre se trata de hacer algo nuevo a partir de dos cosas diferentes, tener una Serverque me tome Requestparece razonable. El Serverprobablemente requerirá de configuración y tiene los valores iniciales adecuados. Probablemente proporcione algo como un singleton o una fábrica para facilitar su uso. Las cosas interesantes reales suceden en el Request. Sus clientes solo tienen que asegurarse de construir el Requestobjeto adecuado . Haga su intención obvia y su negocio claro.

Estar abierto para extensión, pero cerrado para modificación

Podría hacerlo aún más simple codificando diferentes comportamientos utilizando la herencia en lugar de múltiples métodos públicos en el Requestobjeto, más o menos como en el patrón de comando . De esta manera, los clientes también pueden escribir sus propias solicitudes, y los complementos pueden proporcionar nuevas solicitudes (por ejemplo, utilizando los puntos de entrada de setuptools) si es necesario. Esto también aseguraría que el Requestobjeto nunca se convierta en una clase de dios , o que se cambie su API si se agregan nuevas características.

abstrus
fuente