Devo tentar separar o estado da implementação?

5

Neste momento, estou trabalhando com um código que combina o estado e as operações.

Parece algo assim (nota: na verdade não lida com carros / caminhões. Estou abstraindo a lógica dos negócios aqui e peço desculpas antecipadamente que a analogia não faz sentido)

TruckImpl {
    // Fields/Properties
    int Mileage
    bool MissionAccomplished
    // ... and many, many more

    // Constructor
    TruckImpl TruckImpl(IRentalAgreement agreement)

    // methods
    void Drive() {
        // Sets a whole bunch of fields/properties which are subsequently used to make decisions in Park()
        GetCarFromDealer()
        Haul()
    }
    void GetCarFromDealer()
    void Haul()

    // Return to rental company if done, otherwise park at home
    void Park()
}


CarImpl {
    // Fields
    int Mileage
    bool MissionAccomplished

    // Constructor
    CarImpl CarImpl(IRentalAgreement agreement)

    // methods
    // Sets a whole bunch of fields/properties which are subsequently used to make decisions in Park()
    void Drive() {
        // Sets a whole bunch of fields/properties which are subsequently used to make decisions in Park()
        GetCarFromDealer()
        Race()
    }
    void GetCarFromDealer()
    void Race()

    // Return to rental company if done, otherwise park at home    
    void Park()
}

Ele é usado em alguns códigos parecidos com o seguinte pseudocódigo:

RentalVehicleHandler {
    IVehicleImplFactory factory;

    Handle(IRentalAgreement agreement) {
        impl = factory.Create(agreement)

        impl.Drive()
        impl.Park()
    }
}

Main() {
    while(true) {
    var agreements = GetAgreements(); 

    foreach(var agreement in agreements) {
        handler.Handle(agreement)
    }

}

No código acima, estamos obtendo lotes de "RentalAgreements", mas ainda processando cada um deles individualmente. Agora, eu quero lidar com carros e caminhões um pouco separadamente. Para caminhões, eu ainda vou dirigir () e Park () cada um individualmente. Para carros, quero GetCarFromDealer () individualmente, mas depois Race () todo o lote de carros de uma vez antes de estacioná-los. Eu posso querer fazer algo semelhante para caminhões no futuro, mas talvez não.

Eu achei que seria útil separar meu estado da minha implementação como abaixo: (para começar, nenhum material relacionado a lotes foi adicionado ainda):

// One per rental agreement
RentalVehicle {
    #Properties
    RentalAgreement agreement
    int Mileage
    bool MissionAccomplished
    //(...all of the TruckImpl propertiies?)
}

// Can handle any number of RentalAgreements, maybe multiple
CarDriver {

    RentalVehicle Drive(RentalVehicle vehicle)
    RentalVehicle Park(RentalVehicle vehicle)
}

TruckDriver {
    RentalVehicle Drive(RentalVehicle vehicle)
    RentalVehicle Park(RentalVehicle vehicle)
}

É uma coisa útil / razoável de se fazer? Ou é apenas um monte de trabalho extra que não me dará vantagem? Eu acho que isso vai me ajudar tanto com o problema em mãos e me ajudar com mais refatoração, mas eu também tentei algumas outras coisas que eram becos sem saída e eu não quero gastar um monte de tempo refatorando isso sem razão.

    
por sapphiremirage 05.06.2015 / 07:49
fonte

1 resposta

1

Isso é perfeitamente razoável. Na verdade, essa mudança é um exemplo de livro didático do paradigma de programação funcional, que está gradualmente se tornando mais popular nos dias de hoje.

As principais idéias da programação funcional que são relevantes aqui são que você deve se esforçar para ter principalmente funções puras sem estado ou efeitos colaterais e principalmente variáveis imutáveis que são definidas uma vez e nunca mude. Ao se esforçar para isso, é comum acabar escrevendo código como State finalState = changeStuff(initialState) , que é basicamente o que você tem em seu último trecho. Se você fizer isso, eu faria todo o caminho e tornaria RentalVehicle um tipo imutável (o que eu acho em C # é feito fazendo suas propriedades readonly ).

O benefício de coisas imutáveis puras é simplesmente que elas são muito mais fáceis de raciocinar do que coisas mutáveis com efeitos colaterais. Se uma função não tem efeitos colaterais, e seu valor de retorno depende unicamente de quais argumentos você passa, então ela não irá surpreendê-lo com muita frequência. A principal desvantagem (se ignorarmos as preocupações de desempenho) é que o mundo real é stateful, e assim são muitas das coisas que nosso código tem que fazer. Indo 100% funcional para toda a sua lógica de aluguel, transporte e corrida provavelmente não é uma boa idéia a menos que você tenha experiência de programação funcional suficiente para encontrar tal código intuitivo (ou você está trabalhando em uma linguagem funcional que possui ferramentas como mônadas para torná-lo Mais fácil). Entretanto, dividir seu código em partes "puras" e "impuras" geralmente é uma boa idéia, não importa em que você esteja trabalhando, pois limita os locais onde você precisa se preocupar com manipulações de estado mutáveis propensas a erros (novamente, assumindo qualquer perda potencial de desempenho não é um problema).

Eu não posso dizer se essa parte específica da base de código realmente se beneficiaria de mais pureza, mas certamente não é uma coisa irracional para tentar.

    
por 05.06.2015 / 20:42
fonte