C'è qualcosa di intrinsecamente negativo nel mixare i paradigmi in un'applicazione?

3

Attualmente sto scrivendo un'applicazione server Minecraft (personalizzata) in C #. Ho pensato che fosse un buon modo per insegnarmi un sacco di cose importanti come la concorrenza e soprattutto l'efficienza della memoria (dovuta semplicemente alla vastità dello spazio oggetti in Minecraft).

Mentre ero in treno ieri, stavo facendo fatica a "pensare" veramente a un modo per organizzare il modulo di rete. Volevo una semplice interfaccia esterna che fornisse i necessari (lascia che gli utenti reagiscano a una connessione ricevuta, al messaggio ricevuto e quando una connessione viene interrotta). Tuttavia, volevo anche che fosse abbastanza astratto in modo tale da consentire a entrambi i client di gioco reali di connettersi o semplici client falsi.

Ho iniziato a sovrastimolare le cose a la IConnection , INetworkModule , IConnectionFactory , IConnectionProtocol , IMessageEncoder , IMessageDecoder e così via. Regno di nomi in abbondanza. Non solo, ma non stavo davvero ottenendo ovunque - mi sentivo come se stessi ponendo il problema fondamentale dietro altri livelli di astrazione in quanto non avevo idea di come volevo organizzare la comunicazione tra tutti questi moduli .

Poi sono arrivato al pensiero - perché non uso solo verbs (azioni / funzioni / metodi / qualsiasi cosa) invece di nomi (tipi) - concentriamoci su quello che voglio ottenere per ora, e dividerlo in nomi più tardi!

E così, mi è venuto in mente qualcosa di simile a questo:

    private static Func<IDisposable> Listen(
        Action<TcpClient> onConnectionReceived,
        Action<TcpClient> onConnectionDropped,
        Action<TcpClient, int, byte[]> onBytesReceived,
        Func<bool> isOpen)
    {
        // todo: 25565 needs to be injected
        // todo: give opportunity to supply listener
        // todo: refactor listener into role interface
        var listener = new TcpListener(IPAddress.Any, 25565);
        return () => Observable.FromAsync(listener.AcceptTcpClientAsync)
            .DoWhile(isOpen)
            .Subscribe(client =>
            {
                var stream = client.GetStream();
                // on subscribe invoke the delegate first
                onConnectionReceived(client);
                // the delegate has the option to disconnect the client
                // based on whatever criteria it see fit.
                // we should now create an observable to 
                // listen for messages on this client.
                Observable.Defer(() =>
                {
                    // 8kb buffer for each connection
                    // this is actually fairly small
                    var buffer = new byte[8024];
                    // we can handle the 'doing' of things with these bytes
                    // inside the passed function. This function will be doing too much otherwise
                    return Observable.FromAsync(ct => stream.ReadAsync(buffer, 0, buffer.Length, ct))
                        .Select(n => new {Read = n, Bytes = buffer});
                }).Subscribe(a =>
                {
                    // drop connection if nothing was read
                    if (a.Read <= 0)
                        onConnectionDropped(client);
                    else
                        onBytesReceived(client, a.Read, a.Bytes);
                });
            });
    }

Davvero semplice. Beh, voglio dire, non proprio, ma questo fondamentalmente fa il ruolo di tutti i nomi (eccetto IMessageEncoder/Decoder che ho elencato prima), ma solo facendo ipotesi sul fatto che abbiamo bisogno di un TcpClient (ovviamente, che isn ' Quello che voglio adesso, ma posso lavorarci in iterazione 2!)

Tuttavia, il mio problema è che questo non è realmente il codice C # tipico e sicuramente interrompe l'SRP in quanto questa funzione restituisce una funzione per l'intera esecuzione di un server.

Ma - ha senso per me.

Quindi la mia domanda è: esiste un reale svantaggio intrinseco nell'usare un paradigma funzionale-esista come questo al contrario del tradizionale Kingdom of Nouns OOP all'interno di C # - tradizionalmente un linguaggio multi-paradigma? E se ci sono aspetti negativi, cosa dovrei affrontare e perché?

Per la cronaca, intendo che questo sia l'OSS, quindi c'è un altro problema in quanto alcuni sviluppatori potrebbero non capire lo stile del codice perché semplicemente non è orientato all'OC, solo funzioni pure.

    
posta Dan Pantry 31.07.2014 - 10:14
fonte

2 risposte

7

Se c'è qualcosa di negativo nel mixare i paradigmi di programmazione, allora è chiaramente perso nei designer C #; Linq è essenzialmente una pipeline di programmazione funzionale.

Chiaramente conosci già post di Steve Yegge , poiché ti riferisci al Regno dei nomi. Certo, il mondo non ha improvvisamente visto l'errore dei suoi modi e smesso di usare Java. Perché?

  1. Perché Java è già ampiamente utilizzato e ben compreso.
  2. Poiché Kingdom of Nouns è adatto alla realizzazione di sistemi aziendali basati sui dati (nonostante gli sforzi compiuti dagli astronauti di architettura per superare gli impianti idraulici).
  3. Lingue come Java hanno un sacco di cerimonie come classi, interfacce, metodi, scope e così via che i programmatori possono usare per capire l'architettura del programma. Questa cerimonia è in gran parte assente nella maggior parte delle lingue funzionali, o almeno non apertamente visibile.

Quindi questo significa che abbandonerai del tutto la programmazione funzionale? Ovviamente no. La mancanza di cerimonie permette che certe cose siano fatte più rapidamente e intuitivamente in un paradigma funzionale che nel Regno dei nomi. Questo è particolarmente vero per le operazioni matematiche.

Mescolare metafore di programmazione è un modo perfettamente valido per programmare. Se ciò non fosse vero, avremmo solo bisogno di un unico vero linguaggio di programmazione per regolarli tutti.

    
risposta data 31.07.2014 - 10:26
fonte
0

Credo che dovresti stare estremamente attento quando mischi i paradigmi di programmazione in un unico punto. La ragione principale è che quando si mescolano i paradigmi, diventa problematico applicare soluzioni "idiomatiche" su un paradigma a problemi, che mescolano più paradigmi.

Ho un problema con il tuo esempio, perché non è intrinsecamente funzionale. Potresti facilmente sostituire quelle "Azioni" e "Func" con " Comandi ". Diventerebbe puro codice OOP, quando inizi a comporre funzioni e inizi a utilizzare le funzioni di ordine superiore.

Esempio in cui ho trovato che la miscelazione era problematica era, quando una volta nel codice, avevamo bisogno di "decorare" un metodo con azioni pre e post-corsa. Il primo modo ovvio era creare una funzione, che avrebbe ottenuto Azione, eseguirla tra pre e post-codice. Ma poi diventa estremamente problematico cambiare leggermente questo comportamento pre e post e abbiamo dovuto introdurre parametri di funzione e mixare comportamenti multipli in un singolo metodo. Se invece lo implementassimo come classe che potrebbe decorare il metodo, allora sarebbe facile usare l'ereditarietà per cambiare le azioni pre e post. Il leggero problema con questa soluzione è che è necessario creare una nuova classe, che ha un po 'più di codice rispetto al semplice metodo.

Quindi per me è fantastico avere capacità funzionali per qualcosa come LINQ, ma ci penserei bene prima di usare la composizione funzionale come parte dell'architettura o del design nell'applicazione .NET / C #.

Vorrei anche enfatizzare il schema di comando . Credo che questo modello sia strongmente sottovalutato e sottoutilizzato. Sia il design funzionale che il design degli oggetti sono fatti di comporre comportamenti. È solo che entrambi usano strumenti leggermente diversi per ottenere lo stesso risultato finale. Ed è probabilmente un problema di educazione, che molte persone che fanno il design OOP non lo capiscono.

    
risposta data 31.07.2014 - 10:40
fonte

Leggi altre domande sui tag