Refactoring einer Client-API, um doppelten Code und unklare Übergabe von Parametern zu vermeiden

8

Ich muss eine API entwickeln. Die Funktionen der API sind Anforderungen, die den von einem Server bereitgestellten Dienst aufrufen.

Anfangs funktionierte die API folgendermaßen:

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

Es gab viel duplizierten Code und mir hat nicht gefallen, wie die Argumente für die Anfrage übergeben wurden. Da es viele Argumente gibt, wollte ich sie aus der Anfrage extrahieren und etwas parametrischeres machen.

Also habe ich folgendermaßen umgestaltet:

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

Das Strategiemuster hat funktioniert, die Duplizierung wird entfernt. Unabhängig davon habe ich Angst, komplizierte Dinge zu haben, insbesondere in Bezug auf die Argumente. Ich mag es nicht, wie ich damit umgehe, denn wenn ich mir den Code anschaue, erscheint das nicht einfach und klar.

Ich wollte wissen, ob es ein Muster oder eine bessere und klarere Möglichkeit gibt, mit dieser Art von clientseitigem API-Code umzugehen.

k4ppa
quelle
1
Haben Sie darüber nachgedacht, dies an der Code Review Exchange zu erfragen? Die Benutzer dort sind auf solche Dinge spezialisiert.
Nzall
@NateKerkhofs Ja, ich habe darüber nachgedacht, aber dann wähle ich Programmierer, weil dies die Tafel ist, auf der ich Fragen zu Design und Architektur stellen kann, wie ich im Hilfebereich gelesen habe . In Code Review gibt es nicht einmal ein Tag für das Refactoring.
k4ppa
2
Dies liegt daran, dass das gesamte Ziel von CR darin besteht, Code umzugestalten. Grundsätzlich wird impliziert, dass sich alle darin enthaltenen Fragen auf das Refactoring von Code beziehen. Ihr Code sollte bereits funktionieren, aber ansonsten wird bei den Antworten davon ausgegangen, dass Ihr Code auf Fehler und Refactoring überprüft werden soll.
Nzall
2
@NateKerkhofs - CR tendiert dazu, einen sehr engen Fokus in Bezug auf eine Entwurfsprüfung zu legen. Da das OP um Anleitung zur Architektur der Lösung bittet, denke ich, dass dies für diese Site ein Thema ist.

Antworten:

1

Ich würde die Verwendung eines Wörterbuchs (Hash, Map, wie auch immer Ihre Sprache eine Reihe von Schlüssel / Wert-Paaren nennt) für die Argumente überdenken. Auf diese Weise kann der Compiler nicht überprüfen, ob ein Aufrufer alle erforderlichen Werte enthalten hat. Es macht es dem Entwickler schwer, herauszufinden, ob er alle erforderlichen Argumente hat. Es macht es einfach, versehentlich etwas einzuschließen, das Sie nicht brauchten, und etwas zu vergessen, das Sie brauchten. Und am Ende müssen Sie beim Aufrufen alle Werte in das Wörterbuch einfügen und das Wörterbuch in jeder Funktion überprüfen, um alle Argumente zu extrahieren, was den Overhead erhöht. Die Verwendung einer speziellen Struktur kann die Anzahl der Argumente verringern, ohne dass die Compiler diese überprüfen und die Entwickler klar erkennen können, was benötigt wird.

user1118321
quelle
Wenn Sie sich Sorgen machen möchten, dass alle Parameter für eine bestimmte Anforderung übergeben werden, können Sie als Add-On die Verwendung abstrakter Klassen oder Schnittstellen in Betracht ziehen (denken Sie nicht daran, ob dies in Python genauso wie in C # oder Java vorhanden ist). , aber das könnte je nach aktuellem Umfang zu einem Overkill werden. Der Hauptvorteil wäre eine klare Definition pro Anfrage.
eparham7861
0

Ich denke, Ihre Server-API sollte so viele Einträge haben, wie erforderlich sind. Somit kann jeder Entwickler die API leicht lesen ( siehe Lask-Routing als Beispiel ).

Um Doppelungen im Code zu vermeiden, können Sie interne Methoden verwenden

juanmiguelRua
quelle
0

Bei API, Architektur und Muster dreht sich alles um Kommunikation und Absicht. Ihre zweite Version sieht für mich einfach genug aus und scheint auch etwas erweiterbar zu sein, aber meine Meinung (oder sogar Ihre) ist hier nicht wichtig.

Erhalten Sie Feedback

Schauen Sie sich Ihre Software von außen nach innen an (nicht von innen nach außen). Wenn es eine Website gibt, starten Sie von dort. Erwarten Sie, dass Sie leicht einen und nur einen offensichtlichen Weg finden, um das zu tun, was von Ihrer Software erwartet wird. Finden Sie jemanden auf dem Flur und bitten Sie um Feedback zur Benutzerfreundlichkeit.

Identifizieren Sie das Hauptgeschäftsobjekt

Da es bei der Softwareprogrammierung immer darum geht, aus zwei verschiedenen Dingen etwas Neues zu machen Server, Requesterscheint es mir sinnvoll , eine zu haben , die eine erfordert. Die Servererfordern wahrscheinlich Konfiguration und haben sinnvolle Standardeinstellungen. Sie stellen wahrscheinlich so etwas wie einen Singleton oder eine Factory zur Verfügung, um die Verwendung zu vereinfachen. Das wirklich interessante Zeug passiert in der Request. Ihre Kunden müssen nur sicherstellen, dass das richtige RequestObjekt erstellt wird. Machen Sie Ihre Absicht offensichtlich und Ihr Geschäft klar.

Seien Sie offen für Erweiterungen, aber geschlossen für Änderungen

Sie können es noch einfacher machen, indem Sie verschiedene Verhaltensweisen mithilfe der Vererbung anstelle mehrerer öffentlicher Methoden im RequestObjekt codieren , ähnlich wie im Befehlsmuster . Auf diese Weise können Clients auch ihre eigenen Anforderungen schreiben und neue Anforderungen können bei Bedarf von Plugins bereitgestellt werden (z. B. unter Verwendung der Einstiegspunkte von setuptools). Dies würde auch sicherstellen, dass das RequestObjekt niemals zu einer Gottklasse wird oder dass die API geändert wird, wenn neue Funktionen hinzugefügt werden.

Abstrus
quelle