Class VS Function - Qual o jeito correto de escrever Javascript no mundo moderno?

thiagomr

Thiago Moraes

Posted on October 4, 2020

Class VS Function - Qual o jeito correto de escrever Javascript no mundo moderno?

Introdução

O lançamento do ES6 (EcmaScript 2015) foi realmente um divisor de águas no Javascript, trazendo inúmeras novidades, dentre elas, a inclusão da declaração de classes (class Foo {...}). Mesmo após cinco anos, vejo que ainda há muita polêmica sobre o uso da mesma. Nesse artigo vou trazer minha visão sobre o assunto e também alguns exemplos práticos de como eu utilizo esse recurso.

O que acontece por baixo dos panos

A primeira coisa é entender o que de fato acontece quando nós utilizamos a keyword class. Em resumo, é simplesmente um syntax sugar para o padrão de prototypes do javascript. Anteriormente a existência de classes, a forma mais comum para se criar "instâncias" era utilizando factory functions (toda função em javascript é capaz de gerar objetos). Existem pequenas diferenças entre os dois modelos e a que considero mais relevante é que o uso de classes torna obrigatório o uso da keyword new para criar novos objetos. Segue exemplo:

//using Factory Functions
const Hero = function (name) {
    this.name = name;

    this.getName = function() {
        return this.name;
    }
}

const hero = new Hero('spiderman');
//it works
const hero = Hero('spiderman');
//it works

//using Class
class Hero {
    constructor(name) {
        this.name = name;
    }
    getName() {
        return this.name;
    }
}

const hero = new Hero('spiderman');
//it works
const hero = Hero('spiderman');
//TypeError: Class constructor Hero cannot be invoked without 'new'

Performance

Eu já ouvi algumas pessoas me questionando sobre perder performance devido ao uso de um syntax sugar como esse. Na minha opinião isso é completamente irrelevante. Provavelmente você nunca chegará em uma situação que esse tipo de coisa faça alguma diferença significante. Outro ponto importante é que muitas vezes você pode trocar alguns milisegundos de "execução" por "estética", o que pode vir a economizar horas de desenvolvimento.

Edit1: Caso você, assim como eu, goste de escovar alguns bytes, lembre-se que há outros syntax sugar bastante utilizados atualmente ("async / await", por exemplo). E há ferramentas (como o Babel, por exemplo) que permitem transpilar seu código de diferentes maneiras (explorando diferentes possiblidades de performance).

Edit2: Fui questionado por um amigo da comunidade sobre as evidências para esta afirmação (que eram baseadas na minha experiência trabalhando com Node). Achei interessante e fiz um rápido benchmark em cima de alguns exemplos utilizados. Caso queiram conferir, segue o github. Fiquem a vontade para testar e contribuir também.

Orientação a Objetos

Eu entendo que muito da decepção que algumas pessoas tiveram ao tentar utilizar essa feature, foi devido a uma expectativa errada de que isso faria com que a linguagem tivesse um suporte total a orientação a objetos. Com certeza apenas a declaração de classes e o suporte a heranças não é suficiente para esse propósito. Mas para isso nós temos o nosso querido Typescript <3.

Expressividade

Com certeza esse é o ponto mais relevante sobre toda essa questão. Javascript é uma das linguagens mais populares no mundo e muito disso se deve ao fato de ter uma baixa curva de aprendizado, ser multi paradigma e altamente flexível. Ao mesmo tempo que a flexibilidade é uma das coisas mais legais, também pode ser uma das mais prejudiciais, uma vez que faz com que a mesma tenha padrões pouco rígidos e isso pode se tornar um problema em projetos grandes, times inexperientes, etc. O fato de termos classes em Javascript faz com que a mesma se torne mais amigável para desenvolvedores que venham de outras linguagens (uma vez que o conceito de classes é um dos mais utilizados há um bom tempo em programação), além de trazer maior clareza para o código em diversas situações. Um outro fator é que eu, assim como grande parte da comunidade, venho programando bastante em Typescript (onde as classes fazem mais sentido ainda, devido a orientação a objetos por exemplo), isso torna o uso desse recurso bastante natural para mim. Inclusive, grandes frameworks JS como React e Angular, utilizam bastante esse padrão. A seguir, vou mostrar alguns exemplos que eu utilizo com classes.

PS: não vou colocar os exemplos equivalentes com Prototype, pois a ideia é muito mais enfatizar o recurso class do que fazer um comparativo entre ambas (além de considerar este um recurso ultrapassado).

Para padronizar e tratar erros HTTP (ao herdar o tipo Error, nós temos acesso a tudo que essa classe nos oferece, como a call stack do erro, por exemplo):

class BadRequestError extends Error {
    constructor(parameter) {
        super();

        this.status = 400;
        this.title = 'BadRequestError';
        this.message = `missing param [${parameter}]`
    }
}

class UnauthorizedError extends Error {
    constructor() {
        super();

        this.status = 401;
        this.title = 'UnauthorizedError';
        this.message = 'invalid token';
    }
}


class ServerError extends Error {
    constructor() {
        super();

        this.status = 500;
        this.title = 'ServerError';
        this.message = `unespected server error, please contact support`
    }
}

function httpError(error, response) {
    console.log(error);

    //some custom error logic

    if (!error.status) {
        error = new ServerError();        
    }

    return response.status(error.status).send({
        title: error.title,
        message: error.message
    });
}


function httpHandler(request, response) {
    try {
        //do something
        // throw new BadRequestError('parameterName')
        // throw new UnauthorizedError()
        // throw new Error('Random Error')
    } catch (error) {
        return httpError(error, response);
    }
}

Para herdar componentes nativos da linguagem, como o EventEmitter (aqui é possível criar várias chamadas, cada uma independente e tendo acesso a seus métodos):

const EventEmitter = require('events');

class Call extends EventEmitter {
    constructor() {
        super();
        this.startAt = null;
        this.endAt = null;
    }

    save() {
        //save call on database
    }
}


const call = new Call();

call.on('start', () => {
    console.log('starting call');
    this.startAt = new Date();
});

call.on('finish', () => {
    console.log('finishing call');
    this.endAt = new Date();
    this.save();

    console.log('call duration', this.endAt - this.startAt);
});

setTimeout(() => {
    call.emit('start');
}, 1000);

setTimeout(() => {
    call.emit('finish');
}, 4000);

Para injetar dependencias (Aqui o metódo construtor nos dá maior clareza em relação a inicialização das instâncias):

class LoggerService {
    info(...args) {
        console.log('[info]', ...args);
    }

    error(...args) {
        console.error('[error]', ...args);
    }
}

//broker service
class BrokerService {
    constructor({ logger }) {
        this.logger = logger;
    }

    ack(message) {
        this.logger.info('[ack message]', message);
    }
}

class HistoryService {
    constructor({ logger }) {
        this.logger = logger;
    }

    save(msg) {
        this.logger.info('[save message]', msg);
    }
}

class Subscriber {
    constructor({ broker, logger, history }) {
        this.broker = broker;
        this.logger = logger;
        this.history = history;
    }

    handle(msg) {
        //do something

        this.history.save(msg);
        this.broker.ack(msg);
        this.logger.info('done');
    }
}

//service factories
const logger = new LoggerService();
const broker = new BrokerService({ logger });
const history = new HistoryService({ logger });

//subscriber
const subscriber = new Subscriber({ broker, logger, history });

subscriber.handle({ queue: 'test', content: 'I am a message' });

Apesar de usar bastante o recurso de classes, em alguns casos eu gosto de utilizar functions. Seguem alguns exemplos.

Para criar helpers, onde as funções tem um mesmo contexto mas não uma ligação tão forte entre si:

//helpers.js
function validateEmail(email) {
    //validate email
}

function validatePhoneNumber(number) {
    //validate number
}

module.exports = {
    validateEmail,
    validatePhoneNumber
};

//index.js
const { validateEmail, validatePhoneNumber } = require('./helpers');

Para criar middlewares:

//auth.js
module.exports = (request, response, next) {
    auth();
    next();
}

Afinal, devo usar class ou function?

Alguns casos eu considero óbvios. Se você precisa criar instâncias de uma classe ou utilizar herança, claramente eu escolheria class. Se você vai trabalhar com programação funcional, como o próprio nome sugere, utilizaria funções. Porém, nem tudo é apenas preto e branco. É preciso manter a mente aberta para aceitar que há diversas formas boas de se chegar em um resultado. Desenvolver um software é como cuidar de uma biblioteca, você precisa organizar os livros com uma certa lógica para que os mesmos façam sentido onde estão, sejam facilmente encontrados e possam ser adicionados novos de forma intuitiva e simples. Sendo assim, mais importante que escolher um ou outro, é conhecer os recursos disponíveis para ter mais opções na hora construir softwares consistentes.

Conclusão

Nós escrevemos código para desenvolvedores. Por esse motivo, além de desenvolver um software de qualidade (que atenda os requisitos para qual foi designado e possua uma boa performance), é também importante escrever códigos legíveis, entendíveis e que possuam uma interface amigável e intuitiva para novos devs. O fato é que o uso de classes nos proporciona ferramentas muito interessantes para este objetivo. Então se você ainda não usa ou tem algum tipo de preconceito, recomendo fortemente abrir um pouco a mente e experimentar tudo que de melhor o Javascript pode te oferecer!

E vocês, o que acham sobre o assunto? Fiquem a vontade para colaborar com opiniões, experiências e feedbacks.

💖 💪 🙅 🚩
thiagomr
Thiago Moraes

Posted on October 4, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related