Refatoração de uma API do cliente para evitar código duplicado e passagem pouco clara dos parâmetros

8

Eu preciso desenvolver uma API, as funções da API são solicitações que chamam o serviço exposto por um servidor.

Inicialmente, a API funcionava assim:

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')

Havia muito código duplicado e eu não gostei da maneira como ele passou os argumentos para a solicitação. Como existem muitos argumentos, eu queria extraí-los da solicitação e tornar algo mais paramétrico.

Então refatorei desta maneira:

# 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)

O padrão de estratégia funcionou, a duplicação é removida. Independentemente disso, eu tenho medo de ter coisas complicadas, especialmente no que diz respeito aos argumentos, não gosto da maneira como lida com eles, porque, quando olho para o código, não parece fácil e claro.

Então, eu queria saber se existe um padrão ou uma maneira melhor e mais clara de lidar com esse tipo de código API do lado do cliente.

k4ppa
fonte
1
Você já pensou em perguntar sobre isso no Code Review Exchange? Os usuários são especializados em coisas assim.
Nzall
@NateKerkhofs Sim, eu considerei isso, mas escolhi os Programadores, porque este é o quadro onde você deve fazer perguntas sobre design e arquitetura, conforme li na seção de ajuda . Na Revisão de Código, não há sequer uma tag para refatoração.
K4ppa
2
Isso ocorre porque todo o objetivo do CR é refatorar o código. Está basicamente implícito que todas as perguntas são sobre refatoração de código. Seu código já deve funcionar, mas, além disso, as respostas pressupõem que você deseja que seu código seja verificado quanto a erros e refatoração.
Nzall
2
@NateKerkhofs - CR tende a ter um foco muito restrito em relação a uma revisão de design. Como o OP está pedindo orientação sobre a arquitetura da solução, acho que está no tópico deste site.

Respostas:

1

Eu reconsideraria usando um dicionário (hash, mapa, qualquer que seja o seu idioma que chame um conjunto de pares chave / valor) para os argumentos. Fazer dessa maneira torna impossível para o compilador verificar se um chamador incluiu todos os valores necessários. Torna difícil para o desenvolvedor usá-lo para descobrir se eles têm todos os argumentos necessários. Torna fácil incluir acidentalmente algo que você não precisa e esquecer algo que você precisa. E você acaba tendo que colocar todos os valores no dicionário ao chamar e ter que verificar o dicionário em todas as funções para extrair todos os argumentos, aumentando a sobrecarga. O uso de algum tipo de estrutura especializada pode reduzir o número de argumentos sem reduzir a capacidade dos compiladores de verificá-los e a capacidade dos desenvolvedores de ver claramente o que é necessário.

user1118321
fonte
Assim como um complemento, se você estiver preocupado em garantir que todos os parâmetros sejam passados ​​para uma determinada solicitação, poderá usar classes ou interfaces abstratas (não se lembre se isso existe em python da mesma maneira que C # ou Java) , mas isso pode se tornar um exagero, dependendo do escopo atual. O principal benefício seria ter definições claras por solicitação.
Eparham7861 5/05
0

Acho que a API do servidor deve ter tantas entradas quanto solicitações necessárias. Assim, qualquer desenvolvedor poderá ler a API facilmente ( veja o roteamento de lask como exemplo ).

Para evitar duplicação de código, você pode usar métodos internos

juanmiguelRua
fonte
0

API, arquitetura e padrão têm tudo a ver com comunicação e intenção. Sua segunda versão parece bastante simples para mim e parece um pouco extensível também, mas minha opinião (ou até a sua) não é o que importa aqui.

Obter feedbacks

Veja o seu software de fora para dentro (não de dentro para fora). Se houver um site, comece a partir daí. Espere encontrar facilmente uma e apenas uma maneira óbvia de fazer o que seu software deve fazer. Encontre alguém no corredor e peça feedback sobre a usabilidade.

Identifique o principal objeto de negócios

Como a programação de software é sempre sobre criar algo novo a partir de duas coisas diferentes, ter um Serverque Requestpareça sensato para mim. O Serverprovavelmente exigir configuração e tem padrões sensíveis. Você provavelmente fornece algo como um singleton ou uma fábrica para facilitar seu uso. O verdadeiro material interessante acontece no Request. Seus clientes precisam apenas criar o Requestobjeto adequado . Deixe sua intenção óbvia e sua empresa clara.

Esteja aberto para extensão, mas fechado para modificação

Você pode simplificar ainda mais a codificação de comportamentos diferentes usando herança em vez de vários métodos públicos no Requestobjeto, como no padrão de comando . Dessa forma, os clientes também podem escrever suas próprias solicitações, e novas solicitações podem ser fornecidas por plug-ins (usando os pontos de entrada dos setuptools, por exemplo), se necessário. Isso também garantiria que o Requestobjeto nunca se tornasse uma classe god ou alterasse sua API se novos recursos fossem adicionados.

abstrus
fonte