Qualidade de Código
## Dicas práticas ### Baseadas em factos reais #### Código de colegas em projetos de anos anteriores --- ## "Eu gosto de argumentos a mais" ```haskell jogadaAceleraDesacelera :: Jogada -> EstadoJogador -> EstadoJogador jogadaAceleraDesacelera Acelera y = Chao True jogadaAceleraDesacelera Desacelera y = Chao False ``` --- ## Melhoria 1: Remover argumentos desnecessários ```haskell jogadaAceleraDesacelera :: Jogada -> EstadoJogador jogadaAceleraDesacelera Acelera = Chao True jogadaAceleraDesacelera Desacelera = Chao False ``` --- ## "Eu sou demasiado específico" ```haskell verificaAr :: EstadoJogador -> Bool verificaAr (Ar x y z) = True verificaAr (Morto x) = False verificaAr (Chao x) = False ``` --- ## Melhoria 2: Usar notação de records ```haskell verificaAr :: EstadoJogador -> Bool verificaAr (Ar {}) = True verificaAr _ = False ``` --- ## "Eu gosto de ifs/guardas" ```haskell instrucaommpista :: Instrucao -> Instrucao -> Bool instrucaommpista (Anda x piso) (Anda y piso1) | igual x y = True | otherwise = False instrucaommpista (Anda x piso) (Sobe y piso1 z) | igual x y = True | otherwise = False instrucaommpista (Anda x piso) (Desce y piso1 z) | igual x y = True | otherwise = False instrucaommpista (Sobe x piso z) (Anda y piso1) | igual x y = True | otherwise = False instrucaommpista (Sobe x piso z) (Sobe y piso1 z1) | igual x y = True | otherwise = False instrucaommpista (Sobe x piso z) (Desce y piso1 z1) | igual x y = True | otherwise = False instrucaommpista (Desce x piso z) (Anda y piso1) | igual x y = True | otherwise = False instrucaommpista (Desce x piso z) (Desce y piso1 z1) | igual x y = True | otherwise = False instrucaommpista (Desce x piso z) (Sobe y piso1 z1) | igual x y = True | otherwise = False ``` --- ## Melhoria 1: Remover guarda redundante ```haskell instrucaommpista :: Instrucao -> Instrucao -> Bool instrucaommpista (Anda x piso) (Anda y piso1) = igual x y instrucaommpista (Anda x piso) (Sobe y piso1 z) = igual x y instrucaommpista (Anda x piso) (Desce y piso1 z) = igual x y instrucaommpista (Sobe x piso z) (Anda y piso1) = igual x y instrucaommpista (Sobe x piso z) (Sobe y piso1 z1) = igual x y instrucaommpista (Sobe x piso z) (Desce y piso1 z1) = igual x y instrucaommpista (Desce x piso z) (Anda y piso1) = igual x y instrucaommpista (Desce x piso z) (Desce y piso1 z1) = igual x y instrucaommpista (Desce x piso z) (Sobe y piso1 z1) = igual x y ``` --- ## Melhoria 2: introduzir função auxiliar ```haskell instrucaommpista :: Instrucao -> Instrucao -> Bool instrucaommpista x y = igual (bulldozers x) (bulldozers y) bulldozers :: Instrucao -> [Int] bulldozers (Anda x _) = x bulldozers (Sobe x _ _) = x bulldozers (Desce x _ _) = x ``` --- ## "Eu continuo a gostar de guardas" ```haskell jogada :: Int -> Jogada -> Estado -> Estado jogada n joga e | joga == Acelera || joga == Desacelera = velocidade n e | joga == Dispara = dispara n e | joga == Movimenta D = move D n e | joga == Movimenta E = move E n e | joga == Movimenta B = move B n e | joga == Movimenta C = move C n e ``` --- ## Melhoria 1: Utilizar pattern matching ```haskell jogada :: Int -> Jogada -> Estado -> Estado jogada n joga e = case joga of Dispara -> dispara n e Movimenta d -> move d n e _ -> velocidade n e ``` --- ## "Mas eu só sei programar usando guardas" ```haskell acelera :: Jogador -> Jogador acelera (Jogador p d v c e) | e == Chao True || e == Chao False = Jogador p d v c (Chao True) | otherwise = Jogador p d v c e ``` --- ## Melhoria 1: Utilizar pattern matching ```haskell acelera :: Jogador -> Jogador acelera (Jogador p d v c (Chao _)) = Jogador p d v c (Chao True) acelera j = j ``` --- ## Melhoria 2: Utilizar notação de records ```haskell acelera :: Jogador -> Jogador acelera j = j { estadoJogador = aceleraEstado (estadoJogador j) } aceleraEstado :: EstadoJogador -> EstadoJogador aceleraEstado (Chao _) = Chao True aceleraEstado e = e ``` --- ## "Eu às vezes misturo coisas independentes" ```haskell qualPeca :: (Int,Int) -> Int -> Peca qualPeca (x,y) h | (x==0 || x==1) && (y==0 || y==1) = Rampa Terra h (h+1+y) | (x==0 || x==1) && (y>=2 && y<=5) = Rampa Terra h (h-(y-1)) | (x==0 || x==1) && (y>=6 && y<=9) = Recta Terra h | (x==2 || x==3) && (y==0 || y==1) = Rampa Relva h (h+1+y) | (x==2 || x==3) && (y>=2 && y<=5) = Rampa Relva h (h-(y-1)) | (x==2 || x==3) && (y>=6 && y<=9) = Recta Relva h | (x==4) && (y==0 || y==1) = Rampa Lama h (h+y+1) | (x==4) && (y>=2 && y<=5) = Rampa Lama h (h-(y-1)) | (x==4) && (y>=6 && y<=9) = Recta Lama h | (x==5) && (y==0 || y==1) = Rampa Boost h (h+1+y) | (x==5) && (y>=2 && y<=5) = Rampa Boost h (h-(y-1)) | otherwise = Recta Boost h ``` --- ## Melhoria 1: Separar casos independentes com funções auxiliares ```haskell qualPeca :: (Int,Int) -> Int -> Peca qualPeca (x,y) h = qualTipo y h (qualPiso x) qualPiso :: Int -> Piso qualPiso x | x==0 || x==1 = Terra | x==2 || x==3 = Relva | x==4 = Lama | x>=5 = Boost qualTipo :: Int -> Int -> Piso -> Peca qualTipo y h p | y==0 || y==1 = Rampa p h (h+1+y) | y>=2 && y<=5 = Rampa p h (h-(y-1)) | y>=6 && y<=9 = Recta p h ``` --- ## "Eu gosto de complicar" ```haskell valores1Sementes :: [Int] -> Piso -> [Piso] valores1Sementes [] _ = [] valores1Sementes (x:xs) t | elem x [0..1] = Terra:valores1Sementes (tail xs) Terra | elem x [2..3] = Relva:valores1Sementes (tail xs) Relva | x == 4 = Lama:valores1Sementes (tail xs) Lama | x == 5 = Boost:valores1Sementes (tail xs) Boost | elem x [6..9] = t:valores1Sementes (tail xs) t ``` --- ## Melhoria 1: Remover enumeração ```haskell valores1Sementes :: [Int] -> Piso -> [Piso] valores1Sementes [] _ = [] valores1Sementes (x:xs) t | x >= 0 && x <= 1 = Terra:valores1Sementes (tail xs) Terra | x >= 2 && x <= 3 = Relva:valores1Sementes (tail xs) Relva | x == 4 = Lama:valores1Sementes (tail xs) Lama | x == 5 = Boost:valores1Sementes (tail xs) Boost | x >= 6 && x <= 9 = t:valores1Sementes (tail xs) t ``` --- ## Melhoria 2: Separar recursividade com função auxiliar ```haskell valores1Sementes :: [Int] -> Piso -> [Piso] valores1Sementes [] _ = [] valores1Sementes (x:xs) t = p : valores1Sementes xs p where p = valores1Semente x t valores1Semente :: Int -> Piso -> Piso valores1Semente x t | x >= 0 && x <= 1 = Terra | x >= 2 && x <= 3 = Relva | x==4 = Lama | x==5 = Boost | x >= 6 && x <= 9 = t ``` --- ## Os maus hábitos saem caro e descontrolam-se rapidamente... --- ## Programação Vertical! ```haskell int2peca :: Int -> [Int] -> Peca -> Pista int2peca comp _ _ | comp <= 0 = [] int2peca comp (x:y:ys) (Recta p a) |(x>=0 && x<=1 && y>=0 && y<=1) = (Rampa Terra a (a+y+1)) : int2peca (comp-1) ys (Rampa Terra a (a+y+1)) |(x>=2 && x<=3 && y>=0 && y<=1) = (Rampa Relva a (a+y+1)) : int2peca (comp-1) ys (Rampa Relva a (a+y+1)) |(x==4 && y>=0 && y<=1) = (Rampa Lama a (a+y+1)) : int2peca (comp-1) ys (Rampa Lama a (a+y+1)) |(x==5 && y>=0 && y<=1) = (Rampa Boost a (a+y+1)) : int2peca (comp-1) ys (Rampa Boost a (a+y+1)) |(x>=6 && x<=9 && y>=0 && y<=1) = (Rampa p a (a+y+1)) : int2peca (comp-1) ys (Rampa p a (a+y+1)) |(x>=0 && x<=1 && y>=2 && y<=5 && a==0) = (Recta Terra 0) : int2peca (comp-1) ys (Recta Terra 0) |(x>=0 && x<=1 && y>=2 && y<=5 && a<(y-1)) = (Rampa Terra a 0) : int2peca (comp-1) ys (Rampa Terra a 0) |(x>=0 && x<=1 && y>=2 && y<=5 && a>=(y-1)) = (Rampa Terra a (a-(y-1))) : int2peca (comp-1) ys (Rampa Terra a (a+y+1)) |(x>=2 && x<=3 && y>=2 && y<=5 && a==0) = (Recta Relva 0) : int2peca (comp-1) ys (Recta Relva 0) |(x>=2 && x<=3 && y>=2 && y<=5 && a<(y-1)) = (Rampa Relva a 0) : int2peca (comp-1) ys (Rampa Relva a 0) |(x>=2 && x<=3 && y>=2 && y<=5 && a>=(y-1)) = (Rampa Relva a (a-(y-1))) : int2peca (comp-1) ys (Rampa Relva a (a+y+1)) |(x==4 && y>=2 && y<=5 && a==0) = (Recta Lama 0) : int2peca (comp-1) ys (Recta Lama 0) |(x==4 && y>=2 && y<=5 && a<(y-1)) = (Rampa Lama a 0) : int2peca (comp-1) ys (Rampa Lama a 0) |(x==4 && y>=2 && y<=5 && a>=(y-1)) = (Rampa Lama a (a-(y-1))) : int2peca (comp-1) ys (Rampa Lama a (a+y+1)) |(x==5 && y>=2 && y<=5 && a==0) = (Recta Boost 0) : int2peca (comp-1) ys (Recta Boost 0) |(x==5 && y>=2 && y<=5 && a<(y-1)) = (Rampa Boost a 0) : int2peca (comp-1) ys (Rampa Boost a 0) |(x==5 && y>=2 && y<=5 && a>=(y-1)) = (Rampa Boost a (a-(y-1))) : int2peca (comp-1) ys (Rampa Boost a (a+y+1)) |(x>=6 && x<=9 && y>=2 && y<=5 && a==0) = (Recta p 0) : int2peca (comp-1) ys (Recta p 0) |(x>=6 && x<=9 && y>=2 && y<=5 && a<(y-1)) = (Rampa p a 0) : int2peca (comp-1) ys (Rampa p a 0) |(x>=6 && x<=9 && y>=2 && y<=5 && a>=(y-1)) = (Rampa p a (a-(y-1))) : int2peca (comp-1) ys (Rampa p a (a+y+1)) |(x>=0 && x<=1 && y>=6 && y<=9) = (Recta Terra a) : int2peca (comp-1) ys (Recta Terra a) |(x>=2 && x<=3 && y>=6 && y<=9) = (Recta Relva a) : int2peca (comp-1) ys (Recta Relva a) |(x==4 && y>=6 && y<=9) = (Recta Lama a) : int2peca (comp-1) ys (Recta Lama a) |(x==5 && y>=6 && y<=9) = (Recta Boost a) : int2peca (comp-1) ys (Recta Boost a) |(x>=6 && x<=9 && y>=6 && y<=9) = (Recta p a) : int2peca (comp-1) ys (Recta p a) |x < 0 || x > 9 || y < 0 || y > 9 = [] int2peca comp (x:y:ys) (Rampa p a a1) |(x>=0 && x<=1 && y>=0 && y<=1) = (Rampa Terra a (a1+y+1)) : int2peca (comp-1) ys (Rampa Terra a1 (a1-(y-1))) |(x>=2 && x<=3 && y>=0 && y<=1) = (Rampa Relva a (a1+y+1)) : int2peca (comp-1) ys (Rampa Relva a1 (a1-(y-1))) |(x==4 && y>=0 && y<=1) = (Rampa Lama a (a1+y+1)) : int2peca (comp-1) ys (Rampa Lama a1 (a1-(y-1))) |(x==5 && y>=0 && y<=1) = (Rampa Boost a (a1+y+1)) : int2peca (comp-1) ys (Rampa Boost a1 (a1-(y-1))) |(x>=6 && x<=9 && y>=0 && y<=1) = (Rampa p a (a1+y+1)) : int2peca (comp-1) ys (Rampa p a1 (a1-(y-1))) |(x>=0 && x<=1 && y>=2 && y<=5 && a1==0) = (Recta Terra 0) : int2peca (comp-1) ys (Recta Terra 0) |(x>=0 && x<=1 && y>=2 && y<=5 && a1<(y-1)) = (Rampa Terra a1 0) : int2peca (comp-1) ys (Rampa Terra a1 0) |(x>=0 && x<=1 && y>=2 && y<=5 && a1>=(y-1)) = (Rampa Terra a1 (a1-(y-1))) : int2peca (comp-1) ys (Rampa Terra a (a1-(y-1))) |(x>=2 && x<=3 && y>=2 && y<=5 && a1==0) = (Recta Relva 0) : int2peca (comp-1) ys (Recta Relva 0) |(x>=2 && x<=3 && y>=2 && y<=5 && a1<(y-1)) = (Rampa Relva a1 0) : int2peca (comp-1) ys (Rampa Relva a1 0) |(x>=2 && x<=3 && y>=2 && y<=5 && a1>=(y-1)) = (Rampa Relva a1 (a1-(y-1))) : int2peca (comp-1) ys (Rampa Relva a (a1-(y-1))) |(x==4 && y>=2 && y<=5 && a1==0) = (Recta Lama 0) : int2peca (comp-1) ys (Recta Lama 0) |(x==4 && y>=2 && y<=5 && a1<(y-1)) = (Rampa Lama a1 0) : int2peca (comp-1) ys (Rampa Lama a1 0) |(x==4 && y>=2 && y<=5 && a1>=(y-1)) = (Rampa Lama a1 (a1-(y-1))) : int2peca (comp-1) ys (Rampa Lama a (a1-(y-1))) |(x==5 && y>=2 && y<=5 && a1==0) = (Recta Boost 0) : int2peca (comp-1) ys (Recta Boost 0) |(x==5 && y>=2 && y<=5 && a1<(y-1)) = (Rampa Boost a1 0) : int2peca (comp-1) ys (Rampa Boost a 0) |(x==5 && y>=2 && y<=5 && a1>=(y-1)) = (Rampa Boost a1 (a1-(y-1))) : int2peca (comp-1) ys (Rampa Boost a (a1-(y-1))) |(x>=6 && x<=9 && y>=2 && y<=5 && a1==0) = (Recta p 0) : int2peca (comp-1) ys (Recta p 0) |(x>=6 && x<=9 && y>=2 && y<=5 && a1<(y-1)) = (Rampa p a1 0) : int2peca (comp-1) ys (Rampa p a1 0) |(x>=6 && x<=9 && y>=2 && y<=5 && a1>=(y-1)) = (Rampa p a1 (a1-(y-1))) : int2peca (comp-1) ys (Rampa p a (a1-(y-1))) |(x>=0 && x<=1 && y>=6 && y<=9) = (Recta Terra a1) : int2peca (comp-1) ys (Recta Terra a1) |(x>=2 && x<=3 && y>=6 && y<=9) = (Recta Relva a1) : int2peca (comp-1) ys (Recta Relva a1) |(x==4 && y>=6 && y<=9) = (Recta Lama a1) : int2peca (comp-1) ys (Recta Lama a1) |(x==5 && y>=6 && y<=9) = (Recta Boost a1) : int2peca (comp-1) ys (Recta Boost a1) |(x>=6 && x<=9 && y>=6 && y<=9) = (Recta p a1) : int2peca (comp-1) ys (Recta p a1) |x<0||x>9||y<0||y>9 = [] int2peca comp (x:xs) _ = [] int2peca _ _ _ = [] ``` --- ## Programação Horizontal! ```haskell movimenta :: Direcao -> Mapa -> Jogador -> Jogador movimenta C m (Jogador p d v c e) |((fromIntegral(p))>0) && (e==(Chao True)) && ((podeTransitar (wherePlayer (Jogador p d v c e) m) (pecaCima (Jogador p d v c e) m))==True) = (Jogador (p-1) d v c (Chao True)) |((fromIntegral(p))>0) && (e==(Chao False)) && ((podeTransitar (wherePlayer (Jogador p d v c e) m) (pecaCima (Jogador p d v c e) m))==True) = (Jogador (p-1) d v c (Chao False)) |((fromIntegral(p))>0) && (e==(Chao True)) && ((podeTransitar (wherePlayer (Jogador p d v c e) m) (pecaCima (Jogador p d v c e) m))==False) && ((altMenorC (Jogador p d v c e) m)==False) = (Jogador p d v c (Morto 1.0)) |((fromIntegral(p))>0) && (e==(Chao False)) && ((podeTransitar (wherePlayer (Jogador p d v c e) m) (pecaCima (Jogador p d v c e) m))==False) && ((altMenorC (Jogador p d v c e) m)==False) = (Jogador p d v c (Morto 1.0)) |((fromIntegral(p))>0) && (e==(Chao True)) && ((podeTransitar (wherePlayer (Jogador p d v c e) m) (pecaCima (Jogador p d v c e) m))==False) && ((altMenorC (Jogador p d v c e) m)==True) = (Jogador (p-1) d v c (Ar (daAlturaf(wherePlayer(Jogador p d v c e)m)) (incPecAnt(wherePlayer(Jogador p d v c e)m)) 0)) |((fromIntegral(p))>0) && (e==(Chao False)) && ((podeTransitar (wherePlayer (Jogador p d v c e) m) (pecaCima (Jogador p d v c e) m))==False) && ((altMenorC (Jogador p d v c e) m)==True) = (Jogador (p-1) d v c (Ar (daAlturaf(wherePlayer(Jogador p d v c e)m)) (incPecAnt(wherePlayer(Jogador p d v c e)m)) 0)) |otherwise = (Jogador p d v c e) movimenta B m (Jogador p d v c e) | ((fromIntegral(p))<((lengthLL m)-1)) && (e==(Chao True)) && (podeTransitar (wherePlayer (Jogador p d v c e) m)) (pecaBaixo (Jogador p d v c e)==True) = (Jogador (p+1) d v c (Chao True)) | ((fromIntegral(p))<((lengthLL m)-1)) && (e==(Chao False)) && (podeTransitar (wherePlayer (Jogador p d v c e) m)) (pecaBaixo (Jogador p d v c e)==True) = (Jogador (p+1) d v c (Chao False)) | ((fromIntegral(p))<((lengthLL m)-1)) && (e==(Chao True)) && (podeTransitar (wherePlayer (Jogador p d v c e) m)) (pecaBaixo (Jogador p d v c e)==False) && ((altMenorB (Jogador p d v c e) m)==False) = (Jogador p d 0 c (Morto 1.0)) | ((fromIntegral(p))<((lengthLL m)-1)) && (e==(Chao False)) && (podeTransitar (wherePlayer (Jogador p d v c e) m)) (pecaBaixo (Jogador p d v c e)==False) && ((altMenorB (Jogador p d v c e) m)==False) = (Jogador p d 0 c (Morto 1.0)) | ((fromIntegral(p))<((lengthLL m)-1)) && (e==(Chao True)) && (podeTransitar (wherePlayer (Jogador p d v c e) m)) (pecaBaixo (Jogador p d v c e)==False) && ((altMenorB (Jogador p d v c e) m)==True) = (Jogador (p+1) d v c (Ar (daAlturaf(wherePlayer(Jogador p d v c e)m)) (incPecAnt(wherePlayer(Jogador p d v c e)m)) 0)) | ((fromIntegral(p))<((lengthLL m)-1)) && (e==(Chao False)) && (podeTransitar (wherePlayer (Jogador p d v c e) m)) (pecaBaixo (Jogador p d v c e)==False) && ((altMenorB (Jogador p d v c e) m)==True) = (Jogador (p+1) d v c (Ar (daAlturaf(wherePlayer(Jogador p d v c e)m)) (incPecAnt(wherePlayer(Jogador p d v c e)m)) 0)) | otherwise = (Jogador p d v c e) movimenta E m (Jogador p d v c (Ar a i g)) = aumentaInc (Jogador p d v c (Ar a i g)) movimenta E m (Jogador p d v c (Chao True)) = (Jogador p d v c (Chao True)) movimenta E m (Jogador p d v c (Chao False)) = (Jogador p d v c (Chao False)) movimenta E m (Jogador p d v c (Morto x)) = (Jogador p d v c (Morto x)) movimenta D m (Jogador p d v c (Ar a i g)) = diminuiInc (Jogador p d v c (Ar a i g)) movimenta D m (Jogador p d v c (Chao True)) = (Jogador p d v c (Chao True)) movimenta D m (Jogador p d v c (Chao False)) = (Jogador p d v c (Chao False)) movimenta D m (Jogador p d v c (Morto x)) = (Jogador p d v c (Morto x)) ``` --- ## Dicas - Utilizar padrões - Utilizar guardas e condicionais com moderação - Separar o código em diversas funções - Conhecer e reutilizar funções pré-definidas - Importar e reutilizar tarefas anteriores - Simplificar código à medida que aprendem novos conceitos --- ## No fundo - Pensem antes de programar - Não sejam fãs de copy-paste - Não reinventem a roda - Pelo menos não mais do que uma vez! - Sejam preguiçosos - Desde que não copiem! --- ## Análise de código - Código fonte dos exemplos anteriores - [Examples1920.hs](Examples1920.hs) - [LI11920.hs](LI11920.hs) - Há várias ferramentas de análise de código Haskell - Vamos experimentar as mais simples --- ## Ferramenta `ghc -Wall` - Flag do compilador, **modo pedântico** que ativa todos os warnings - Avisos de potenciais erros - Sobreposição de definições - Variáveis não usadas - Padrões não exaustivos - ... - Por omissão no ficheiro `worms.cabal` do vosso projeto
## Ferramenta `hlint` - **Pequenas sugestões** de como melhorar o código - usar funções pré-definidas - simplificar sintaxe - remover redundâncias - ... - Instalar: `cabal install hlint` - Correr: `hlint . --report` - Gera ficheiro `report.html`
## Ferramenta `homplexity` - Avisos de funções com **grande complexidade** - número de linhas - condições aninhadas - ... - Instalar: `cabal install homplexity --flags="html"` - Correr: `homplexity-cli --format=HTML . > homplexity.html` - Gera ficheiro `homplexity.html`
## Ferramentas - Sugestões simples (`ghc -Wall` ou `hlint`) - **Eficazes** a fomentar boas práticas de programação ao nível da sintaxe - *Não servem* para evitar más práticas conceptuais (copy-paste, etc) - Avisos de complexidade (`homplexity`) - **Eficazes** a sinalizar quase certamente mau design - Inexistência de avisos *não garante* bom design --- ## Ferramentas (IA?) ### Não são pedagógicas! - Objetivo de aprendizagem não é escrever código, mas assimilar e treinar conceitos de programação - Copiar pela IA é tão pedagógico quanto copiar pelo colega do lado! - Será dado o mesmo tratamento = **fraude** - IA ameaça substituir vários empregos - Não se assumam como substituíveis! --- ## Ferramentas (IA?) ### A sério, usem com muito cuidado! - `Copilot` gera sugestões "as-you-type" - Quase perfeito para funções ao estilo da Tarefa 0 - Forte tendência a *complicar* (treinada sobre código muito mais complexo) - *Não* tem contexto do enunciado do projeto! - `ChatGPT` só quer agradar - Tem *ainda menos* contexto do vosso projeto! - *Não* confiem nem para refactorings de código