Durante um projeto recente em que estou trabalhando, tive que usar várias funções que se parecem com isso:
static bool getGPS(double plane_latitude, double plane_longitude, double plane_altitude,
double plane_roll, double plane_pitch, double plane_heading,
double gimbal_roll, double gimbal_pitch, double gimbal_yaw,
int target_x, int target_y, double zoom,
int image_width_pixels, int image_height_pixels,
double & Target_Latitude, double & Target_Longitude, double & Target_Height);
Então, eu quero refatorá-lo para algo parecido com isto:
static GPSCoordinate getGPS(GPSCoordinate plane, Angle3D planeAngle, Angle3D gimbalAngle,
PixelCoordinate target, ZoomLevel zoom, PixelSize imageSize)
Isso me parece significativamente mais legível e seguro do que o primeiro método. Mas faz sentido criar PixelCoordinate
e PixelSize
classes? Ou seria melhor usar apenas std::pair<int,int>
para cada um. E faz sentido ter uma ZoomLevel
aula ou devo apenas usar uma double
?
Minha intuição por trás do uso de classes para tudo é baseada nessas suposições:
- Se houver classes para tudo, seria impossível passar um
ZoomLevel
dentro de onde umWeight
objeto era esperado; portanto, seria mais difícil fornecer argumentos errados para uma função - Da mesma forma, algumas operações ilegais causariam erros de compilação, como adicionar um
GPSCoordinate
a umZoomLevel
ou outroGPSCoordinate
- As operações legais serão fáceis de representar e tipificar com segurança. ou seja, subtrair dois
GPSCoordinate
s produziria umaGPSDisplacement
No entanto, a maioria dos códigos C ++ que eu vi usa muitos tipos primitivos, e imagino que deve haver uma boa razão para isso. É uma boa ideia usar objetos para qualquer coisa, ou tem desvantagens das quais não estou ciente?
Respostas:
Sim definitivamente. Funções / métodos que levam muitos argumentos é um cheiro de código e indica pelo menos um dos seguintes:
Se o último for o caso (e o seu exemplo certamente sugere isso), é hora de fazer uma parte dessa "abstração" de fantasia que as crianças legais estavam falando oh, apenas algumas décadas atrás. Na verdade, eu iria além do seu exemplo e faria algo como:
(Não estou familiarizado com o GPS, portanto essa provavelmente não é uma abstração correta; por exemplo, não entendo o que o zoom tem a ver com uma função chamada
getGPS
.)Por favor, prefira aulas como
PixelCoordinate
acimastd::pair<T, T>
, mesmo que as duas tenham os mesmos dados. Ele agrega valor semântico, além de um pouco de segurança extra de tipo (o compilador impedirá que você passe de aScreenCoordinate
para,PixelCoordinate
mesmo que ambos sejampair
s.fonte
Isenção de responsabilidade: eu prefiro C a C ++
Em vez de classes, eu usaria estruturas. Este ponto é pedante, na melhor das hipóteses, uma vez que estruturas e classes são quase idênticas (veja aqui um gráfico simples ou essa pergunta do SO ), mas acho que há mérito na diferenciação.
Os programadores C estão familiarizados com estruturas como blocos de dados que têm um significado maior quando agrupados, enquanto que quando vejo um clasas, presumo que exista algum estado interno e métodos para manipular esse estado. Uma coordenada GPS não tem estado, apenas dados.
Da sua pergunta, parece que é exatamente isso que você está procurando, um contêiner geral, não uma estrutura estável. Como os outros mencionaram, eu não me incomodaria em colocar o Zoom em uma classe própria; Pessoalmente, odeio aulas criadas para conter um tipo de dados (meus professores adoram criar
Height
e darId
aulas).Eu não acho que isso seja um problema. Se você reduzir o número de parâmetros agrupando as coisas óbvias como você fez, os cabeçalhos devem ser suficientes para evitar esses problemas. Se você estiver realmente preocupado, separe as primitivas com uma estrutura, que deve fornecer erros de compilação para erros comuns. É fácil trocar dois parâmetros um pelo outro, mas mais difícil se eles estiverem mais afastados.
Bom uso de sobrecargas do operador.
Também é bom o uso de sobrecargas do operador.
Eu acho que você está no caminho certo. Outra coisa a considerar é como isso se encaixa no restante do programa.
fonte
O uso de tipos dedicados tem a vantagem de ser muito mais difícil estragar a ordem dos argumentos. A primeira versão da sua função de exemplo, por exemplo, começa com uma cadeia de 9 duplos. Quando alguém usa essa função, é muito fácil estragar a ordem e passar os ângulos do cardan no lugar das coordenadas do GPS e vice-versa. Isso pode levar a erros muito estranhos e difíceis de rastrear. Quando você passa apenas três argumentos com tipos diferentes, esse erro pode ser detectado pelo compilador.
Na verdade, consideraria ir um passo adiante e introduzir tipos tecnicamente idênticos, mas com um significado semântico diferente. Por exemplo, tendo uma classe
PlaneAngle3d
e uma classe separadaGimbalAngle3d
, embora ambos sejam uma coleção de três duplos. Isso tornaria impossível usar um como o outro, a menos que seja intencional.Eu recomendaria o uso de typedefs, que são apenas aliases para tipos primitivos (
typedef double ZoomLevel
), não apenas por esse motivo, mas também por outro: permite facilmente alterar o tipo posteriormente. Quando um dia você teve a ideia de que o usofloat
poderia ser tão bom quanto eficiente,double
mas com mais memória e CPU, basta mudar isso em um só lugar, e não em dozends.fonte
Ótimas respostas aqui já, mas há uma coisa que gostaria de acrescentar:
Usar
PixelCoordinate
ePixelSize
classe torna as coisas muito específicas. Um generalCoordinate
ouPoint2D
classe provavelmente pode atender melhor às suas necessidades. APoint2D
deve ser uma classe implementando as operações de ponto / vetor mais comuns. Você poderia implementar essa classe um mesmo genericamente (Point2D<T>
onde T pode serint
oudouble
ou qualquer outra coisa).As operações de ponto / vetor 2D são tão comuns em muitas tarefas de programação em geometria 2D que, para minha experiência, essa decisão aumentará muito a chance de reutilizar as operações 2D existentes. Você vai evitar a necessidade de re-implementá-las para cada
PixelCoordinate
,GPSCoordinate
"tomamos" class coordenada x novamente e novamente.fonte
struct PixelCoordinate:Point2D<int>
se você quer segurança tipo adequadoÉ [mais legível]. Também é uma boa ideia.
Mas faz sentido criar as classes PixelCoordinate e PixelSize?
Se eles representam conceitos diferentes, faz sentido (ou seja, "sim").
Você poderia começar fazendo
typedef std::pair<int,int> PixelCoordinate, PixelSize;
. Dessa forma, você cobre a semântica (você está trabalhando com coordenadas e tamanhos de pixel, não com std :: pairs no código do cliente) e, se precisar estender, basta substituir o typedef por uma classe e o código do cliente deve requer alterações mínimas.Você também pode digitá-lo, mas eu usaria um
double
até precisar de uma funcionalidade associada a ele que não exista por um duplo (por exemplo, ter umZoomLevel::default
valor exigiria que você definisse uma classe, mas até ter algo como mantenha um duplo).São argumentos fortes (você deve sempre se esforçar para tornar suas interfaces fáceis de usar corretamente e difíceis de usar incorretamente).
Há razões; em muitos casos, esses motivos não são bons. Um motivo válido para fazer isso pode ser o desempenho, mas isso significa que você deve ver esse tipo de código como uma exceção, não como uma regra.
Geralmente, é uma boa idéia garantir que, se seus valores sempre apareçam juntos (e não façam sentido à parte), você os agrupe (pelos mesmos motivos mencionados em sua postagem).
Ou seja, se você possui coordenadas que sempre possuem x, ye z, é muito melhor ter uma
coordinate
classe do que transmitir três parâmetros em todos os lugares.fonte