segunda-feira, 16 de junho de 2014

Refactoring das classes Position em Python e C#

Na postagem anterior, foram criadas as classes Position nas linguagens Python e C# (que são as linguagens que eu costumo trabalhar), como forma de testar o quanto nossa modelagem é independente de plataforma.

Pretendo manter a interface pública das classes o mais parecida possível entre todas as linguagens, a não ser que isso viole de forma muito óbvia alguma característica de cada linguagem.

Logo depois de publicar a postagem, me dei conta de alguns esquecimentos, algumas inadequações, e algums escolhas de design "suspeitas", de forma que, alguma reflexão e alguma pesquisa depois, venho apresentar as classes refatoradas.

Em primeiro lugar, movi o código para o GitHub, de forma que o leitor sempre terá onde encontrar o código atualizado, bem como o histórico de modificações. As postagens antigas não vão ser editadas para refletir as mudanças que forem ocorrendo, mas as mudanças mais significativas serão discutidas em postagens específicas, se for o caso.

Para este refactoring, as mudanças de API (que impactam a implementação de ambas as linguagens) foram as seguintes:
  • Implementação (conforme previamente modelado) do valor nulo para elevação, permitindo que haja um construtor com dois argumentos (latitude e longitude, nessa ordem), e um argumento opcional elevação cujo valor padrão é nulo;
  • Eliminação do construtor que usava uma sequência como argumento, pois isso enfraquecia a semântica, e dava margem a erros lógicos (pois não havia garantia de que os itens da sequência viriam na ordem correta), e de tempo de execução. Agora, a classe possui apenas um construtor, e esse construtor recebe dois argumentos numéricos, ou três argumentos numéricos. Uma discussão sobre essa decisão pode ser vista nesta pergunta do StackOverflow;
  • Apenas a Elevação possui setter. Latitude e a Longitude são somente leitura (e portanto possuem apenas getter);


Versão atual em C#:

using System;
using System.Collections.Generic;
using System.Linq;

namespace GpsDataModel
{
    public struct Position {

        private readonly double _lat, _lon;
        private double? _elev;

        public Position(double lat, double lon, double? elev = null) : this() {
            
            _lat = Math.Abs(((lat-90) % 360) - 180) - 90;
            _lon = ((lon-180) % 360) - 180;

            _elev = validateElevation(elev);
        }
        

        public double Latitude { get { return _lat; } }
        public double Longitude { get { return _lon; } }

        public double? Elevation { 
            get { return _elev; }
            set { _elev = validateElevation(value); }
        }


        private double? validateElevation(double? elev) {
            if (elev < -WGS84.SEMI_MAJOR_AXIS)
                throw new ArgumentOutOfRangeException(
                    String.Format("Elevation {0:0.00} " +
                           "is deeper than the center of the Earth!", elev));
            else
                return elev;
        }        


        public override string ToString() {
            return String.Format("Position({0:0.000}, {1:0.000}, {2:0.0})",
                                      _lat, _lon, _elev);
        }
    }
}

Comentários específicos sobre a implementação C#:
  • O argumento opcional é apresentado com um valor padrão, representado sintaticamente pelo operador "=";
  • Os tipos numéricos nullable são sufixados por um ponto de interrogação - no exemplo, "double?";
  • A lógica de validação do setter da Elevação, que deve ser usada também no construtor, foi movida para a função validateElevation;

Versão atual em Python:

#!/usr/bin/env python
# coding: utf-8

import WGS84

class Position(object):

    __slots__ = ('_lat', '_lon', '_elev')

    def __init__ (self, lat, lon, elev=None):

        self._lat = abs(((float(lat)-90) % 360) - 180) - 90
        self._lon = ((float(lon)-180) % 360) - 180

        self.elevation = elev

           
    
    @property
    def latitude(self):
        return self._lat
        
    @property
    def longitude(self):
        return self._lon
        
    @property
    def elevation(self):
        return self._elev

    @elevation.setter
    def elevation(self, elev):
        if elev > (-WGS84.SEMI_MAJOR_AXIS):
            self._elev = float(elev)
        else:
            raise ValueError ("Elevation %.2f " + "
                is deeper than the center of the Earth!" % elev)
 
 

    def __str__ (self):
        return ("Position(%.3f, %.3f, %.1f)"
                          % (self._lat, self._lon, self._elev))

Nenhum comentário:

Postar um comentário