Portal SAMP
[Tutorial] Uma abordagem sobre arquitetura - Versão de Impressão

+- Portal SAMP (https://portalsamp.com)
+-- Fórum: SA-MP (https://portalsamp.com/forumdisplay.php?fid=5)
+--- Fórum: Guias e Tutoriais (https://portalsamp.com/forumdisplay.php?fid=7)
+--- Tópico: [Tutorial] Uma abordagem sobre arquitetura (/showthread.php?tid=1247)



Uma abordagem sobre arquitetura - Tony_Fat - 31/05/2021

INTRODUÇÃO

Olá. Gostaria de começar dizendo que esse tópico terá bastante teoria e é destinado a um público um pouco mais avançado. Bom, quando saímos um pouco dessa pequena região do SA-MP e adquirimos um pouco mais de experiência em outras linguagens, nós observamos em como ficamos limitados ao que realmente vimos aqui. Fora daqui, aprendemos conceitos extremamente fundamentais e importantes sobre arquitetura de sistemas além de ficarmos diante de inúmeras ferramentas, cenários e experiências e isso é muito bom para alguém que já está bem estabelecido aqui, abrindo novos horizontes para como escrevemos nossos códigos e como nos sentimentos em relação a eles. Meu objetivo aqui é, além de passar uma visão um pouco melhor de como devemos encarar as coisas, gerar também uma discussão bem filosófica de como as coisas deveriam ser e aprendermos mais trocando experiências.

BENCHMARKS

Bom, uma coisa que é levada muito a sério nessa comunidade são os benchmarks. Por algum motivo, a tendência aqui é usar sempre oq demonstra ser mais rápido em benchmarks, mesmo que isso abra mão de algumas boas vantagens, e aqui está um problema bem fundamental. Programação é um negócio que vimos com muita clareza que, quase nunca existe "o melhor", e sim, "o melhor para determinada coisa". E normalmente, oque costuma ser bom em algo, pode não ser muito bom para uma outra coisa e isso deve ser pensado.

Um exemplo bem claro é referente ao uso de PVars para guardar informações do jogador. PVars tem a característica de declarar "variáveis" que podem ser acessadas de uma forma externa (como um filterscript por exemplo) e isso é um recurso bem interessante se bem trabalhado. Observe muitos dos filterscripts atualmente; Nem todos conseguem funcionar de uma forma plug-in-play, visto que, eles não conseguem acessar informações (como de contas por exemplo) de dentro do seu gamemode e acaba sendo necessário sempre adaptá-los. De igual modo, você também terá limitações para construir filterscripts para seu próprio servidor.

PVars apesar de possuir esse recurso, ela acaba sendo um pouco menos performática do que uma variável declarada puramente com new/static. Porém, ela tem esse uso que pode trazer inúmeros benefícios abrindo mão de alguns poucos milésimos (que sinceramente, nem sempre fará falta). Normalmente é interessante utiliza-las em informações globalizadas como o dinheiro atual do jogador (Para fazer um GivePlayerMoneyEx em um Filterscript por exemplo) ou até ver se ele já fez login no servidor.

A moral da história aqui é, vale muito apena abrir mão de alguns milésimos de segundos afim de ganhar algo em troca (como consistência e/ou novos recursos). Não se prenda tanto aos números.

ORIENTAÇÃO A OBJETOS

Se você já experimentou algumas linguagens de programação, MUITO provavelmente você deve saber os benefícios da Orientação a Objetos. Se você ainda não experienciou isso, aconselho que pesquise um pouco, mas de forma BEM RESUMIDA e pra contextualizar, é uma forma de você isolar uma séria de métodos e atributos afim de descrever alguma entidade.

Bem, pawno não tem OO, mas isso não nos impede de pegar alguns conceitos e tentar fazer algo seguindo a mesma filosofia. Vou usar de exemplo o anti-hacker mais fácil de se construir que basicamente QUALQUER servidor tem. Me refiro ao anti-money hacker. Observe o Seguinte:
Código:
new Dinheiro[MAX_PLAYERS];

stock GivePlayerMoneyEx(playerid, valor) {
    Dinheiro[playerid] += valor;
    GivePlayerMoney(playerid, valor);
}

public OnPlayerUpdate(playerid) {
    if(GetPlayerMoney(playerid) != Dinheiro[playerid]) {
        ResetPlayerMoney(playerid);
        GivePlayerMoney(playerid, Dinheiro[playerid]);
    }
    return 1;
}
Não está completo o anti-money hacker, mas observe oq fizemos aqui. O SA-MP tem um erro de arquitetura bem inocente aqui. Basicamente a informação de dinheiro do jogador, é vinda do próprio jogador. Ou seja, se o jogador usar um cleo de dinheiro, quando essa informação for ao samp-server (via Raknet), o servidor irá guardar essa informação e confiar nela (MANO?). O que fazemos para resolver isso então? Bem, armazenamos a informação do dinheiro do player em uma variável e vamos passar a confiar no valor dela, e não no valor dado por GetPlayerMoney. E aqui está o ponto que quase ninguém se atenta.

Esse é um exemplo simples, então não costuma ser cometido o erro que vou demonstrar agora, mas serve pra contextualizar (visto que é o mesmo contexto). Nesse exemplo como pode ser observado, precisamos nos preocupar em garantir que o jogador, veja o dinheiro do seu personagem sendo o mesmo valor que Dinheiro[playerid], para isso usamos OnPlayerUpdate (ou um timer se preferir) para verificar se o dinheiro em mãos do jogador, é o mesmo que ele de fato deveria ter. Se não for, ele reseta e da a quantia correta.

Observe que de igual modo, também devemos nos preocupar de, quando atribuir o dinheiro ao jogador, alterar também o valor em Dinheiro[playerid]. Como fazemos isso de forma consistente e sem efeitos colaterais? Isolamos o comportamento e a informação em um método chamado GivePlayerMoneyEx. BINGOOO! Seguimos o mesmo principio aqui que aprendemos em OO de isolar o atributo em métodos getters e setters e nunca acessar diretamente o atributo, afim de manter consistência. Se não fizéssemos isso, teríamos de toda vez que usar GivePlayerMoney, também atribuir o novo valor de Dinheiro[playerid] e isso com o tempo MUITO provavelmente iria gerar um efeito colateral onde ambos os valores não estariam bem sincronizados e também dificultaria muito a manutenção. Imagine que você queira adicionar uma validação, como por exemplo, ver se ele está logado para poder dar a grana. Se não fizermos dessa forma, será HORRÍVEL ter de buscar toda vez que usamos GivePlayerMoney e adicionar essa validação sendo que usar uma função para isolar tudo isso. Teria poupado esse sofrimento.

Esse exemplo foi para contextualizar. Normalmente o pessoal isola as informações de forma correta no GivePlayerMoney, mas nem sempre fazem isso no restante do gamemode. Abra seu gamemode e observe quantas vezes usamos algo como:
Código:
if(Casa[playerid] == ALGUMA COISA) {
    // bla bla bla
}

Casa[playerid] = casaid;
A moral da história aqui é, isole os atributos, por mais simples que seja e evite ao máximo altera-los de forma direta, e sim, através de um método (função).

DICA DE ESTRUTURA
Uma coisa que foi muito bem recebida aqui, foi o conceito de modularização do gamemode. Claro que foi bem recebido! Isso melhora muito a manutenção e a integridade do gamemode. Agora tente juntar isso e o conceito que foi abordado aqui e vamos ter um resultado um pouco melhor. Normalmente juntamente da pasta "modules" eu costumo criar uma pasta chamada "entities" (elements e objetos são nomes legais também). Qual é o objetivo dela? Bem, nela eu procuro imaginar que cada arquivo ali será como uma classe. Por exemplo, o arquivo Player.inc será como class Player e lá dentro eu coloco os atributos do jogador, como por exemplo:
Código:
enum E_PLAYER_DATA {
    E_PLAYER_NAME[24],
    E_PLAYER_SKIN,
    E_PLAYER_MONEY
}
new PLAYER_DATA[MAX_PLAYERS][E_PLAYER_DATA];
Aqui eu defini uma estrutura que compõe algumas das características do objeto Player. Em uma outra linguagem utilizando OO seria algo como:

Código:
class Player {
    private string Name;
    private int Skin;
    private int Money;
    private bool Auth;
}
Como pode ver no código acima, os atributos são privados, então apenas a classe Player tem visibilidade deles. Infelizmente não podemos fazer isso no pawno, então tenha o bom senso de não utilizar os atributos diretamente.

Certo, então como faço caso queira alterar uma informação do jogador? Simples, crie um método setter. No pawno, seria fazer algo como:
Código:
enum E_PLAYER_DATA {
    E_PLAYER_NAME[24],
    E_PLAYER_SKIN,
    E_PLAYER_MONEY
}
new PLAYER_DATA[MAX_PLAYERS][E_PLAYER_DATA];

Player_SetSkin(const playerid, const skin) {
    Player[playerid][E_PLAYER_SKIN] = skin;
    SetPlayerSkin(playerid, skin);
    return 1;
}

Em uma outra linguagem utilizando OO, seria algo como:
Código:
class Player {
    private string Name;
    private int Skin;
    private int Money;

    public void setSkin(int skin) {
        this.Skin = skin;
    }
}
Certo, agora lá na pasta "modules", onde ficam todos os sistemas do seu servidor como casas, empresas e etc. Sempre que você quiser alterar a skin do jogador, você irá utilizar Player_SetSkin que está isolada e possuí todos os atributos e validações necessárias.

Agora imagine o seguinte, você desenvolveu uma enorme parte do seu gamemode e precisa adicionar um atributo a função Player_SetSkin. Você quer por exemplo impedir que altere a skin de um jogador que não está autenticado (logado). Como os modulos do seu servidor utilizam a função Player_SetSkin e toda a logica está contida dentro dela, você só precisa ir até ela lá na pasta "entities/Player.inc" e adicionar a validação:
Código:
enum E_PLAYER_DATA {
    E_PLAYER_NAME[24],
    E_PLAYER_SKIN,
    E_PLAYER_MONEY,
    bool:E_PLAYER_AUTH
}

new PLAYER_DATA[MAX_PLAYERS][E_PLAYER_DATA];

bool:Player_IsAuth(const playerid) {
    return PLAYER_DATA[playerid][E_PLAYER_AUTH];
}

Player_SetSkin(const playerid, const skin) {
    if(!Player_IsAuth(playerid))
        return 0;

    PLAYER_DATA[playerid][E_PLAYER_SKIN] = skin;
    SetPlayerSkin(playerid, skin);
    return 1;
}

Com isso, é sempre garantido fácil manutenção e impede muitos efeitos colaterias.

CONSIDERAÇÕES

Acredito ter dito tudo oque precisa aqui e espero ter sido bem claro. Eu estou com um pouco de pressa no momento então irei corrigindo ou acrescentando coisas ao post com o decorrer do tempo. Espero com isso gerar uma atenção maior sobre arquitetura e gerar alguns melhores debates sobre isso. Boa noite senhores <3

jesus, como foi horrível digitar código aqui.


RE: Uma abordagem sobre arquitetura - xbruno1000x - 31/05/2021

Tenho certeza que será útil para muito gente. Ótimo tópico.


RE: Uma abordagem sobre arquitetura - willttoonn - 01/06/2021

Excelente tutorial.


RE: Uma abordagem sobre arquitetura - KiLLER - 01/06/2021

Bom tutorial, seguir bons padrão de programação sempre é bom, fica até mas fácil de realizar manutenções, obrigado !