aguarde...

27 de janeiro de 2020

Adeus, Código Limpo

Adeus, Código Limpo

Era uma tarde da noite.

Meu colega acabou de verificar o código que eles escreveram a semana toda. Estávamos trabalhando em uma tela de edição de gráficos e eles implementaram a capacidade de redimensionar formas como retângulos e ovais, arrastando pequenas alças nas bordas.

O código funcionou.

Mas foi repetitivo. Cada forma (como um retângulo ou um oval) tinha um conjunto diferente de alças, e arrastar cada alça em direções diferentes afetava a posição e o tamanho da forma de uma maneira diferente. Se o usuário pressionou a tecla Shift, também precisamos preservar as proporções durante o redimensionamento. Havia um monte de matemática.

O código tinha algo parecido com isto:

let Rectangle = {
  resizeTopLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeTopRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
};

let Oval = {
  resizeLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeTop(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottom(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
};

let Header = {
  resizeLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },  
}

let TextBlock = {
  resizeTopLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeTopRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomLeft(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
  resizeBottomRight(position, size, preserveAspect, dx, dy) {
    // 10 repetitive lines of math
  },
};

Essa matemática repetitiva estava realmente me incomodando.

Não estava limpo .

A maior parte da repetição foi entre direções semelhantes. Por exemplo, Oval.resizeLeft()tinha semelhanças com Header.resizeLeft(). Isso ocorreu porque ambos lidaram com arrastar a alça no lado esquerdo.

A outra semelhança foi entre os métodos para a mesma forma. Por exemplo, Oval.resizeLeft()tinha semelhanças com os outros Ovalmétodos. Isso foi porque todos eles lidaram com ovais. Houve também alguma duplicação entre RectangleHeaderTextBlockporque os blocos de texto eram retângulos.

Eu tive uma ideia

Podemos remover toda a duplicação agrupando o código da seguinte maneira:

let Directions = {
  top(...) {
    // 5 unique lines of math
  },
  left(...) {
    // 5 unique lines of math
  },
  bottom(...) {
    // 5 unique lines of math
  },
  right(...) {
    // 5 unique lines of math
  },
};

let Shapes = {
  Oval(...) {
    // 5 unique lines of math
  },
  Rectangle(...) {
    // 5 unique lines of math
  },
}

e depois compondo seus comportamentos:

let {top, bottom, left, right} = Directions;

function createHandle(directions) {
  // 20 lines of code
}

let fourCorners = [
  createHandle([top, left]),
  createHandle([top, right]),
  createHandle([bottom, left]),
  createHandle([bottom, right]),
];
let fourSides = [
  createHandle([top]),
  createHandle([left]),
  createHandle([right]),
  createHandle([bottom]),
];
let twoSides = [
  createHandle([left]),
  createHandle([right]),
];

function createBox(shape, handles) {
  // 20 lines of code
}

let Rectangle = createBox(Shapes.Rectangle, fourCorners);
let Oval = createBox(Shapes.Oval, fourSides);
let Header = createBox(Shapes.Rectangle, twoSides);
let TextBox = createBox(Shapes.Rectangle, fourCorners);

O código tem metade do tamanho total e a duplicação desapareceu completamente! Tão limpo . Se quisermos mudar o comportamento para uma direção ou forma específica, poderíamos fazê-lo em um único local, em vez de atualizar métodos em todo o local.

Já era tarde da noite (eu me empolguei). Eu verifiquei minha refatoração para dominar e fui para a cama, orgulhosa de como eu desembaraçava o código confuso do meu colega.

A manhã seguinte

… não foi como o esperado.

Meu chefe me convidou para um bate-papo individual, onde educadamente me pediram para reverter minha alteração. Eu fiquei horrorizado. O código antigo estava uma bagunça, e o meu estava limpo !

Obedeci de má vontade, mas levei anos para ver que eles estavam certos.

É uma fase

Ficar obcecado com o “código limpo” e remover a duplicação é uma fase pela qual muitos de nós passamos. Quando não nos sentimos confiantes em nosso código, é tentador associar nosso senso de valor próprio e orgulho profissional a algo que pode ser medido. Um conjunto de regras estritas de fiapos, um esquema de nomes, uma estrutura de arquivos, uma falta de duplicação.

Não é possível automatizar a remoção de duplicação, mas não ficar mais fácil com a prática. Normalmente, você pode dizer se existe menos ou mais após cada alteração. Como resultado, remover a duplicação parece melhorar algumas métricas objetivas sobre o código. Pior, isso mexe com o senso de identidade das pessoas: “Eu sou o tipo de pessoa que escreve código limpo” . É tão poderoso quanto qualquer tipo de auto-engano.

Depois que aprendemos a criar abstrações , é tentador aprimorar essa capacidade e extrair abstrações do nada sempre que vemos códigos repetitivos. Depois de alguns anos de codificação, vemos repetições em todos os lugares – e a abstração é nossa nova superpotência. Se alguém nos disser que a abstração é uma virtude , nós a comeremos. E começaremos a julgar outras pessoas por não adorarem a “limpeza”.

Vejo agora que minha “refatoração” foi um desastre de duas maneiras:

  • Primeiro, eu não conversei com a pessoa que escreveu. Reescrevi o código e fiz o check-in sem a entrada deles. Mesmo que tenha sido uma melhoria (na qual não acredito mais), essa é uma maneira terrível de fazer isso. Uma equipe de engenharia saudável está constantemente construindo confiança . Reescrever o código de seu colega de equipe sem discussão é um grande golpe para sua capacidade de colaborar efetivamente em uma base de código juntos.
  • Em segundo lugar, nada é gratuito. Meu código negociava a capacidade de alterar os requisitos de duplicação reduzida e não era um bom negócio. Por exemplo, mais tarde precisávamos de muitos casos e comportamentos especiais para identificadores diferentes em formas diferentes. Minha abstração teria que se tornar várias vezes mais complicada para permitir isso, enquanto que na versão “bagunçada” original essas mudanças eram fáceis como um bolo.

Estou dizendo que você deve escrever um código “sujo”? Não. Sugiro pensar profundamente no que você quer dizer quando diz “limpo” ou “sujo”. Você sente revolta? Justiça? Beleza? Elegância? Você tem certeza de que pode nomear os resultados de engenharia concretos correspondentes a essas qualidades? Como exatamente eles afetam a maneira como o código é escrito e modificado ?

Eu com certeza não pensei profundamente sobre nenhuma dessas coisas. Pensei muito sobre como o código era – mas não sobre como ele evoluiu com uma equipe de humanos macios.

Codificação é uma jornada. Pense a que distância você chegou da sua primeira linha de código para onde está agora. Acho que foi uma alegria ver pela primeira vez como extrair uma função ou refatorar uma classe pode simplificar o código complicado. Se você se orgulha de seu ofício, é tentador buscar a limpeza no código. Faça isso por um tempo.

Mas não pare por aí. Não seja um fanático por códigos limpos. Código limpo não é uma meta. É uma tentativa de entender a imensa complexidade dos sistemas com os quais estamos lidando. É um mecanismo de defesa quando você ainda não tem certeza de como uma mudança afetaria a base de código, mas precisa de orientação em um mar de desconhecidos.

Posted in Blog
Write a comment