Executando verificação de segurança...
4

[DÚVIDA] Como identificar gambiarras no código?

Olá! 👋Estou criando meu primeiro "projeto" em Python, como já mencionei antes estou inciando pela 5 vez na programação, mas agora é para valer. Para treinar meus conhecimentos adquiridos depois de consumir aulas sobre o tema achei que seria uma boa fazer um projeto simples para praticar, nesse caso escolhi um Pedra, papel e tesoura. Caso alguém se interesse da uma olhada no meu repositório.

Porém, eu gosto de implementar mais features ao script para me desafiar ainda mais e decidi que seria uma boa colocar uma parte com as regras do jogo(vai que alguém não sabe), e nisso pensei em criar um looping que enquanto o usuário não falar que entendeu as regras ele não para de perguntar, mas acontece que eu quero isso de dois modos.

Não vou explicar por agora esses dois modos que iriei fazer, mas sim gostaria de perguntar sobre como que você identifica que está fazendo gambiarras? Tipo, qual a diferença de uma gambiarra e uma lógica alternativa? Consegui fazer esses dois modos, funciona perfeitamente, mas não sei funcionou porque fiz uma gambiarra ou porque utilizei uma boa lógica. Caso alguém queira ler a ideia base do meu projeto código está aqui uma versão de teste que fiz em outro arquivo para ver como ficaria a implementação dessa feature usando Clube da Luta como exemplo😂

import time
rules = { # dictionary way
    # key : value
    "Rule 1": "You can't talk about Fight Club",
    "Rule 2": "You can't talk about Fight Club",
    "Rule 3": "You can't talk about Fight Club",
    "Rule 4": "Only one fight at a time",
    "Rule 5": "Only two people fight at a time",
    "Rule 6": "No shoes or shirts",
    "Rule 7": "Fights continue until necessary",
    "Rule 8": "First-time attendees must fight"
}
rule = rules.values()
ask = str(input("$ Do you want to know the rules?(y/n) "))
ask1 = -1
while ask != 0:
    if ask != "y" and ask != "n":
        ask = str(input("$ Do you want to know the rules?(y/n) "))
    if ask == "y" or ask1 == "n":
        for value in rules.values():
            print(f"* {value}")
            time.sleep(1)
        ask1 = str(input("$ Did you understand the rules?(y/n) "))
        while ask1 != "n" and ask1 != "y":
            ask1 = str(input("$ Did you understand the rules?(y/n) "))
    if ask == "n" or ask1 == "y":
        print("# Welcome to Fight Club")
        ask = 0
Carregando publicação patrocinada...
10

Gambiarra é um termo amplo, mas eu considero que é qualquer coisa que seja mal feita ou que está longe de ser a solução ideal. É uma daquelas coisas que é meio difícil explicar de forma objetiva, mas vc sabe que é quando vê.

gambiarra


No seu caso, eu não diria que é gambiarra, apenas que dá pra simplificar a lógica e melhorar alguns detalhes.

Por exemplo, vc cria um dicionário com as regras. Mas vc só usa os valores, e em nenhum momento utiliza as chaves. Além disso, elas parecem meio redundantes: toda vez que vc tem chaves do tipo "coisa 1", "coisa 2", etc, é um forte indício de que talvez só precise de uma lista (ou array) dessas "coisas".

Outro detalhe é que input sempre retorna uma string, então usar str(input()) é redundante e desnecessário.

Quanto aos loops, eu faria diferente, mas aí talvez seja gosto pessoal: eu não gosto muito dessas variáveis de controle ask e ask1. E a lógica de pedir pra digitar de novo até que o valor seja "y" ou "n" pode ser movida para uma função, isolando-a do loop principal. Algo assim:

import time

def ask(text):
    while True: # enquanto não digitar "y" ou "n", continua o loop
        answer = input(text)
        if answer in ('y', 'n'):
            return answer # return sai da função, devolvendo o valor digitado

rules = [
    "You can't talk about Fight Club",
    "You can't talk about Fight Club",
    "You can't talk about Fight Club",
    "Only one fight at a time",
    "Only two people fight at a time",
    "No shoes or shirts",
    "Fights continue until necessary",
    "First-time attendees must fight"
]

# só entra no loop se a pessoa quiser saber as regras
if ask('$ Do you want to know the rules?(y/n) ') == 'y':
    while True:
        for rule in rules:
            print(f"* {rule}")
            time.sleep(1)
        if ask('$ Did you understand the rules?(y/n) ') == 'y':
            break # se entendeu as regras, sai do while

print('# Welcome to Fight Club')

Repare na função ask: eu não preciso colocar a pergunta uma vez antes do while e outra dentro dele. Basta repeti-lo indefinidamente, até que seja digitado "y" ou "n".

Depois, posso usar essa função para ambas as perguntas (se quer saber as regras, e se entendeu). Note que nem precisa de variáveis auxiliares, posso usar o retorno da função ask diretamente (eu fiz assim porque o valor não é utilizado mais de uma vez depois disso, mas caso fosse, aí justifica usar uma variável para guardar o valor digitado).

Também mudei a lógica: eu só entro no loop que mostra as regras se a pessoa quiser vê-las. E se a pessoa entendeu as regras, posso interromper o loop.

Já a mensagem "welcome" é sempre mostrada no final, então faz mais sentido deixá-la fora do loop.


Se não quiser mudar para usar while True e break, pelo menos mude os nomes das variáveis, pois ask e ask1 não deixa muito claro o que cada uma é. Poderia ser algo como want_to_know e understood, por exemplo.

Outra opção é fazer com que a função ask retorne True ou False, já que a pergunta só admite duas respostas possíveis ("sim" ou "não"). Aí no if eu nem preciso verificar se é "y" ou "n":

import time

# retorna True se digitar "y" ou False se digitar "n"
# se não for nenhum, pede para digitar de novo
def ask(text):
    while True:
        answer = input(text)
        if answer == 'y':
            return True
        if answer == 'n':
            return False

rules = [
    "You can't talk about Fight Club",
    "You can't talk about Fight Club",
    "You can't talk about Fight Club",
    "Only one fight at a time",
    "Only two people fight at a time",
    "No shoes or shirts",
    "Fights continue until necessary",
    "First-time attendees must fight"
]

# não precisa mais comparar se é igual a "y"
if ask('$ Do you want to know the rules?(y/n) '):
    while True:
        for rule in rules:
            print(f"* {rule}")
            time.sleep(1)
        if ask('$ Did you understand the rules?(y/n) '):
            break # se entendeu as regras, sai do while

print('# Welcome to Fight Club')
2

Nossa mano, que aula sobre códigos bem escritos, declaração correta de variáveis, remoção de partes inúteis no código👏. Muito obrigado mesmo, me ajudou bastante agora, vou corrigir essas redundâncias no meu código e irei melhorar esse ponto do ask e dos loops que você mencionou, mandei até 2 tabcoins para você!

1

Então, eu consegui melhorar isso das variáveis e das redundâncias no código como o str(input()), mas essa parte nova da função do ask eu consigo entender, mais ou menos, de como ela funciona, mas não tenho total compreensão, isso me faz pensar se, no meu atual nível de iniciante, seria uma boa fazer isso no meu código, eu acho fica um pouco estranho uma parte de um código ser feita como um pleno e outra parte como um júnior, entende o que estou querendo dizer?

7

Qual parte exatamente vc não entendeu?

while True é um loop "infinito", já que a condição para se repetir é True (ou seja, sempre é verdadeira).

Dentro dele tem o input, e depois eu vejo se o que foi digitado é "y" ou "n": if answer in ('y', 'n'). Basicamente, ('y', 'n')é uma tupla contendo as strings 'y' e 'n', e o if verifica se answer é um dos valores desta tupla.

Claro que eu poderia ter feito if answer == 'y' or answer == 'n', também funciona. Nesse caso tanto faz um ou outro, mas se vc tivesse mais opções para verificar (digamos que 10, 20 ou até mais, por exemplo), uma tupla fica mais sucinto do que vários or. Por exemplo:

# supondo que existam 20 valores possíveis
if valor in ('valor1', 'valor2', 'valor3', .... , 'valor20'):

# o if acima é mais sucinto que:
if valor == 'valor1' or valor == 'valor2' or valor == 'valor3' ...  or valor == 'valor20':

Enfim, se entrar no if (ou seja, se digitar "y" ou "n"), ele retorna o valor que foi digitado (é o que o return faz). Um detalhe importante é que o return encerra a função (ou seja, ele também sai do while).


Enfim, se deixar assim fica mais claro?

import time

rules = [
    "You can't talk about Fight Club",
    "You can't talk about Fight Club",
    "You can't talk about Fight Club",
    "Only one fight at a time",
    "Only two people fight at a time",
    "No shoes or shirts",
    "Fights continue until necessary",
    "First-time attendees must fight"
]

while True:
    want_to_know = input('$ Do you want to know the rules?(y/n) ')
    if want_to_know == 'y' or want_to_know == 'n':
        break # sai do while

if want_to_know == 'y':
    while True:
        for rule in rules:
            print(f"* {rule}")
            time.sleep(1)
        while True:
            understood = input('$ Did you understand the rules?(y/n) ')
            if understood == 'y' or understood == 'n':
                break # sai do while referente ao understood
        if understood == 'y':
            break # se entendeu as regras, sai do while externo

print('# Welcome to Fight Club')

Agora eu removi a função e usei um while para cada pergunta. A diferença é que vez de return, eu uso break, que serve para interromper um loop. Ou seja, se digitar "y" ou "n", ele sai do while, e se digitar outra coisa, o loop continua, chamando input novamente.

É basicamente o mesmo que a função faz, a única diferença é que a função precisa retornar o valor para quem a chama, por isso ela usa return em vez de break.

Mas repare que a parte das perguntas é praticamente igual (faz a pergunta, se for "y" ou "n" para, senão pergunta de novo). E quando vc tem trechos repetitivos que fazem basicamente a mesma coisa com poucas variações, estes trechos são fortes candidatos a ficarem isolados em uma função própria.

Outro ponto é que ter um loop dentro do outro pode deixar mais confuso, ainda mais se cada um tem seu break. Isso até seria um argumento contra o break, mas eu acho que na verdade é um argumento a favor de criar a função ask :-)


Aqui talvez seja uma questão de "estilo"/gosto pessoal, mas tem gente que prefere fazer algo como:

want_to_know = None
while want_to_know != 'y' and want_to_know != 'n':
    want_to_know = input('$ Do you want to know the rules?(y/n) ')

Ou seja, primeiro eu inicializo a variável com um valor que eu sei que é inválido, só para forçar a entrada no while. Também serve, veja qual fica mais claro para você.

2

Não sei nem como te agredecer mano, que explicação bem feita, adcionou muito ao meu conhecimento, mandei o máximo de TabCoins que o site me permitiu para você, parabéns e muito obrigado👏 :)

1

Boa noite mano👋, fiquei o dia buscando entender linha por linha desse código e somente agora implementei ele com 100% de compreensão do codebase do meu projeto, por ficar buscando tanto compreender consegui encontrar até outra forma de otimização do código, e gostaria de mandar para você para uma avaliação se possível, desde já agradeço novamente pelo conhecimento passado pelos postos acima, me ajudaram muito!

def ask(text):
   while True:
    ask = input(text)
    if ask == "y":
      return True
    if ask == "n":
      return False
if ask("Do you want to know the rules?(y/n) "):
   while True:
      for rule in rules:
        print(f"* {rule}")
        time.sleep(0.5)
      understood = ask("Did you understand the rules?(y/n) ")
      if understood:
        break
2

Tem os conceitos de "code smells" e "clean code" que são práticas muito boas pra evitar gambiarras. Com o tempo tu consegue ver que códigos que seguem essas práticas são mais simples de dar manutenção.

Isso é algo um pouco subjetivo. Você pode fazer o melhor código na sua opinião, mas aí outro dev consegue refatorar e simplificar.

Mas com ou sem gambiarra, continua codando. Você já é um vencedor por estar criando algo; porque outros as vezes soh criticam as suas gambiarras e não criam nada.

1

Verdade, você falou um ponto que eu não tinha percebido, o importante é codar, e com o tempo eu vou pegando essas boas práticas, brigadão mano!

1

A gambiarra é um conceito tipicamente brasileiro — reflexo da nossa cultura de resolver problemas com criatividade, mesmo à margem das "regras". Não é apenas um "workaround" (solução ruim) ou um "hack" (solução inteligente): é usar recursos de forma não convencional, subvertendo seu propósito original. Como usar um pedaço de silver-tape para consertar um cano furado: funciona, mas não é recomendado.

O Que Define uma Gambiarra?

  1. Subversão da Intenção

Uso de elementos fora do propósito para o qual foram criados. Não se trata apenas de "improviso", mas de distorcer a função natural de algo (ex.: transformar uma string em flag booleana para controlar um loop).

Isso é gambiarra. Não é questão de gosto.

  1. Uso Consciente

Se você sabe que está distorcendo uma ferramenta (e aceita os riscos), pode ser uma gambiarra válida. Se faz sem entender (ex.: misturar tipos de dados por acidente), é erro, não gambiarra.

  1. Contexto é Tudo
  • Em protótipos ou emergências, é sempre gambiarra. Em código de produção, pode ser apenas uma bomba-relógio, não gambiarra.
2
1
0

Minha sugestão é retirar as redundâncias e deixar o código o mais enxuto possível.

import time


def ask(question):
    return True if input(question) == "y" else False


def main():
    rules = [
        "You can't talk about Fight Club", "", "",
        "Only one fight at a time",
        "Only two people fight at a time",
        "No shoes or shirts",
        "Fights continue until necessary",
        "First-time attendees must fight",
    ]
    rules[2] = rules[1] = rules[0]
    if ask("$ Do you want to know the rules?(y/n) "):
        while True:
            for rule in rules:
                print(f"* {rule}")
                time.sleep(1)
            if ask("$ Did you understand the rules?(y/n) "):
                break
    print("# Welcome to Fight Club")


if __name__ == "__main__":
    main()
3

Em vez de:

def ask(question):
    return True if input(question) == "y" else False

Poderia ser apenas:

def ask(question):
    return input(question) == "y"

Pois o resultado do operador == é True ou False, então pode retornar o valor da expressão diretamente.


Mas tem uma diferença importante aí. No código original, ele verifica se o que foi digitado é "y" ou "n", e se for qualquer valor diferente disso ("a", "x", ou qualquer outra coisa), ele pede para digitar novamente.

Mas no seu código ele só pergunta uma vez e pronto. E se digitar qualquer coisa diferente de "y" (e que não necessarimente é "n"), ele considera que é False.

Enfim, é uma diferença sutil, mas que muda o comportamento do programa. Claro que por ser um projeto pessoal sem requisitos bem definidos, não precisamos ser tão rígidos. Mas é importante saber que os códigos não são equivalentes.

1
0

Se fosse um if convencional, funcionaria perfeitamente sem o else, mas esse tipo de expressão pythonic equivale ao operador ternário return input(question) == "y" ? true : false em linguagens baseadas em C, tais como C++, Java, Javascript, Typescript, entre outras.

Pode testar: se você retirar o else False provocará um erro do tipo SyntaxError: expected 'else' after 'if' expression.

Em relação à não detecção do caractere "n", partimos do "princípio da boa fé", ou seja, de que o usuário só digitaria "y" ou "n", e não outros caracteres diferentes, ou dígitos, e nem especiais CTRL + C, por exemplo, que precisariam de um tratamento adequado.

Em plataformas de desafios de programação (Topcoder, HackerRank, Leetcode, entre outras), esse princípio da boa fé se aplica. Quando um problema enuncia que as entradas são "y" ou "n", não é necessário se preocupar com outro valor.

Contudo, concordo que na vida real, fora de ambientes de testes, temos que estar preparados para tratar e testar todos os erros possíveis.