Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

🛁 Conceitos do Clean Code adaptados para Python

License

NotificationsYou must be signed in to change notification settings

jherfson/clean-code-python

 
 

Repository files navigation

Build Status

Índice

  1. Introdução
  2. Variáveis
  3. Funções
  4. Objects and Data Structures
  5. Classes
    1. S: Princípio da Responsabilidade Única (SRP)
    2. O: Princípio do Aberto/Fechado (OCP)
    3. L: Princípio de Substituição de Liskov (LSP)
    4. I: Princípio da Segregação de Interface (ISP)
    5. D: Princípio da Inversão de Dependência (DIP)
  6. Não se repita (DRY)

Introdução

Principios da engenharia de software, do livro de Robert C. MartinCódigo Limpo,adaptados para Python. Isto não é um style guide. É um guia para desenvolver software legível, reutilizavel e refatorável em Python.

Nem todos principios contidos aqui tem de ser seguidos estritamente, e muito menos irão ser universalmente aprovados. Estes são apenas guias e nada mais, mas que foram codificados durante muito anos por experiências coletivas dos autores deCódigo Limpo.

Inspriado emclean-code-javascript

Versão Python3.7+

Variáveis

Use nomes significantes e pronunciáveis em suas variáveis

Ruim:

importdatetimeymdstr=datetime.date.today().strftime("%y-%m-%d")

Bom:

importdatetimecurrent_date:str=datetime.date.today().strftime("%y-%m-%d")

⬆ back to top

Use o mesmo vocabulário para o mesmo tipo de variável

Ruim:Usamos três nomes diferentes para a mesma entidade:

defget_user_info():passdefget_client_data():passdefget_customer_record():pass

Bom:Se a entidade for a mesma, você deve ser consistente ao se referir a ela em suas funções:

defget_user_info():passdefget_user_data():passdefget_user_record():pass

Melhor ainda:Python é (também) uma linguagem de programação orientada a objetos. Se fizer sentido, empacote as funções junto com a implementação concreta da entidade em seu código, como atributos de instância, métodos ou métodos de propriedade:

fromtypingimportUnion,Dict,TextclassRecord:passclassUser:info :str@propertydefdata(self)->Dict[Text,Text]:return {}defget_record(self)->Union[Record,None]:returnRecord()

⬆ back to top

Use nomes fáceis de pesquisar

Nós vamos ler mais código do que escrever, por isso é importante que o código que escrevemos seja legível e fácil de achar. Aonão nomear variáveis, prejudicamos nossos leitores.Torne seus nomes fáceis de procurar.

Ruim:

importtime# Para que é o número 86400?time.sleep(86400)

Bom:

importtime# Declare-os no namespace global do módulo.SECONDS_IN_A_DAY=60*60*24time.sleep(SECONDS_IN_A_DAY)

⬆ back to top

Use variáveis explicativas

Ruim:

importreaddress="One Infinite Loop, Cupertino 95014"city_zip_code_regex=r"^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$"matches=re.match(city_zip_code_regex,address)ifmatches:print(f"{matches[1]}:{matches[2]}")

Nada mal: É melhor, mas ainda dependemos muito do regex.

importreaddress="One Infinite Loop, Cupertino 95014"city_zip_code_regex=r"^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$"matches=re.match(city_zip_code_regex,address)ifmatches:city,zip_code=matches.groups()print(f"{city}:{zip_code}")

Bom: Diminua a dependência de regex nomeando as variáveis em subgrupo

importreaddress="One Infinite Loop, Cupertino 95014"city_zip_code_regex=r"^[^,\\]+[,\\\s]+(?P<city>.+?)\s*(?P<zip_code>\d{5})?$"matches=re.match(city_zip_code_regex,address)ifmatches:print(f"{matches['city']},{matches['zip_code']}")

⬆ back to top

Evite mapear mentalmente

Não force o leitor do seu código a traduzir o que a variável significa.Explicito é melhor que implito.

Ruim:

seq= ("Austin","New York","San Francisco")foriteminseq:#do_stuff()#do_some_other_stuff()# Espere, `item` de novo?print(item)

Bom:

locations= ("Austin","New York","San Francisco")forlocationinlocations:#do_stuff()#do_some_other_stuff()# ...print(location)

⬆ back to top

Não adicione contextos desnecessários

Se o nome da sua classe/objeto expressa algo, não repita isso no nome da variável.

Ruim:

classCar:car_make:strcar_model:strcar_color:str

Bom:

classCar:make:strmodel:strcolor:str

⬆ back to top

Use argumentos padrões ao invés de encadear condicionais

Muito ruim

Porque escrever:

importhashlibdefcreate_micro_brewery(name):name="Hipster Brew Co."ifnameisNoneelsenameslug=hashlib.sha1(name.encode()).hexdigest()# etc.

... quando você pode especificar um argumento padrão em vez disso? Isso também deixa claro quevocê está esperando uma string como argumento.

Bom:

fromtypingimportTextimporthashlibdefcreate_micro_brewery(name:Text="Hipster Brew Co."):slug=hashlib.sha1(name.encode()).hexdigest()# etc.

⬆ back to top

Funções

Argumentos de funções (2 ou menos, idealmente)

Limitar a quantidade de parametros de uma função é incrivelmente importantante porque isso torna sua função fácil de testar. Ter mais de três de leva em uma explosão onde você tem que testar vários casos diferentes, com argumentos separados.

Um ou dois argumentos é o caso ideal, e três deve ser evitado se possível. Algo além disso deve ser deixado de lado. Usualmente, se você tem mais de dois argumentos, suas funções estão tentando fazer coisas demais. Nos casos que não estão, na maior parte do tempo um objeto irá ser o suficiente como argumento.

Ruim:

defcreate_menu(title,body,button_text,cancellable):pass

Java-esque:

classMenu:def__init__(self,config:dict):self.title=config["title"]self.body=config["body"]# ...menu=Menu(    {"title":"My Menu","body":"Something about my menu","button_text":"OK","cancellable":False    })

Muito bom

fromtypingimportTextclassMenuConfig:"""A configuration for the Menu.    Attributes:        title: The title of the Menu.        body: The body of the Menu.        button_text: The text for the button label.        cancellable: Can it be cancelled?    """title:Textbody:Textbutton_text:Textcancellable:bool=Falsedefcreate_menu(config:MenuConfig)->None:title=config.titlebody=config.body# ...config=MenuConfig()config.title="My delicious menu"config.body="A description of the various items on the menu"config.button_text="Order now!"# O atributo de instância substitui o atributo de classe padrão.config.cancellable=Truecreate_menu(config)

Chique

fromtypingimportNamedTupleclassMenuConfig(NamedTuple):"""A configuration for the Menu.    Attributes:        title: The title of the Menu.        body: The body of the Menu.        button_text: The text for the button label.        cancellable: Can it be cancelled?    """title:strbody:strbutton_text:strcancellable:bool=Falsedefcreate_menu(config:MenuConfig):title,body,button_text,cancellable=config# ...create_menu(MenuConfig(title="My delicious menu",body="A description of the various items on the menu",button_text="Order now!"    ))

Ainda mais chique

fromtypingimportTextfromdataclassesimportastuple,dataclass@dataclassclassMenuConfig:"""A configuration for the Menu.    Attributes:        title: The title of the Menu.        body: The body of the Menu.        button_text: The text for the button label.        cancellable: Can it be cancelled?    """title:Textbody:Textbutton_text:Textcancellable:bool=Falsedefcreate_menu(config:MenuConfig):title,body,button_text,cancellable=astuple(config)# ...create_menu(MenuConfig(title="My delicious menu",body="A description of the various items on the menu",button_text="Order now!"    ))

Ainda mais chique, versões Python3.8+

fromtypingimportTypedDict,TextclassMenuConfig(TypedDict):"""A configuration for the Menu.    Attributes:        title: The title of the Menu.        body: The body of the Menu.        button_text: The text for the button label.        cancellable: Can it be cancelled?    """title:Textbody:Textbutton_text:Textcancellable:booldefcreate_menu(config:MenuConfig):title=config["title"]# ...create_menu(# Você precisa informar todos os parâmetrosMenuConfig(title="My delicious menu",body="A description of the various items on the menu",button_text="Order now!",cancellable=True    ))

⬆ back to top

Funções devem fazer somente uma coisa

Esta é, de longe, a regra mais importante da engenharia de software. Quando as funções fazem mais de uma coisa, elas são mais difíceis de compor, testar e pensar sobre. Quando você consegue isolar a função para apenas uma ação, elas podem ser refatoradas sem muita dificuldade e seu código será fácilmente lido. Se você não tirar mais nada deste guia além disso, você estará à frente de muitos programadores.

Ruim:

fromtypingimportListclassClient:active:booldefemail(client:Client)->None:passdefemail_clients(clients:List[Client])->None:"""Filter active clients and send them an email.    """forclientinclients:ifclient.active:email(client)

Bom:

fromtypingimportListclassClient:active:booldefemail(client:Client)->None:passdefget_active_clients(clients:List[Client])->List[Client]:"""Filter active clients.    """return [clientforclientinclientsifclient.active]defemail_clients(clients:List[Client])->None:"""Send an email to a given list of clients.    """forclientinget_active_clients(clients):email(client)

Você vê uma oportunidade para usar geradores agora?

Melhor ainda

fromtypingimportGenerator,IteratorclassClient:active:booldefemail(client:Client):passdefactive_clients(clients:Iterator[Client])->Generator[Client,None,None]:"""Only active clients"""return (clientforclientinclientsifclient.active)defemail_client(clients:Iterator[Client])->None:"""Send an email to a given list of clients.    """forclientinactive_clients(clients):email(client)

⬆ back to top

Nomes das funções devem dizer o que elas fazem

Ruim:

classEmail:defhandle(self)->None:passmessage=Email()# O que isso quer dizer?message.handle()

Bom:

classEmail:defsend(self)->None:"""Send this message"""message=Email()message.send()

⬆ back to top

Funções devem estar em apenas um nível de abstração

Quando você tem mais de um nível de abstração possívelmente sua função está fazendo coisa demais. Dividir suas funções desencadeia em código reusável e fácil de testar.

Ruim:

# type: ignoredefparse_better_js_alternative(code:str)->None:regexes= [# ...    ]statements=code.split('\n')tokens= []forregexinregexes:forstatementinstatements:passast= []fortokenintokens:passfornodeinast:pass

Bom:

fromtypingimportTuple,List,Text,DictREGEXES:Tuple= (# ...)defparse_better_js_alternative(code:Text)->None:tokens:List=tokenize(code)syntax_tree:List=parse(tokens)fornodeinsyntax_tree:passdeftokenize(code:Text)->List:statements=code.split()tokens:List[Dict]= []forregexinREGEXES:forstatementinstatements:passreturntokensdefparse(tokens:List)->List:syntax_tree:List[Dict]= []fortokenintokens:passreturnsyntax_tree

⬆ back to top

Não use sinalizadores como parâmetros de função

Os sinalizadores informam ao usuário que esta função faz mais de uma coisa. Funçõesdeve fazer uma coisa. Divida suas funções se elas estiverem seguindo um código diferentecaminhos baseados em verdadeiro ou falso.

Ruim:

fromtypingimportTextfromtempfileimportgettempdirfrompathlibimportPathdefcreate_file(name:Text,temp:bool)->None:iftemp:        (Path(gettempdir())/name).touch()else:Path(name).touch()

Bom:

fromtypingimportTextfromtempfileimportgettempdirfrompathlibimportPathdefcreate_file(name:Text)->None:Path(name).touch()defcreate_temp_file(name:Text)->None:    (Path(gettempdir())/name).touch()

⬆ back to top

Evite efeitos colaterais

Uma função produz um efeito colateral se fizer qualquer coisa além de assumir um valor ao invés de retornar outro valor ou valores. Por exemplo, um efeito colateral pode ser a escritaa um arquivo, modificando alguma variável global ou transferindo acidentalmente todo o seu dinheiropara um estranho.

No entanto, você precisa ter efeitos colaterais em um programa de vez em quando - por exemplo, comono exemplo anterior, você pode precisar gravar em um arquivo. Nestes casos, vocêdeve centralizar e indicar onde você está incorporando efeitos colaterais. Não temvárias funções e classes que gravam em um arquivo específico - em vez disso, têm um(e apenas um) serviço que o faz.

O ponto principal é evitar armadilhas comuns, como o compartilhamento de estado entre objetossem qualquer estrutura, usando tipos de dados mutáveis ​​que podem ser gravados por qualquer coisa ou usando uma instância de uma classe, e não centralizando onde ocorrem seus efeitos colaterais.Se você puder fazer isso, ficará mais feliz do que a grande maioria dos outros programadores.

Ruim:

# type: ignore# Este é um nome de nível de módulo..# É uma boa prática defini-los como valores imutáveis, como uma string.# No entanto...fullname="Ryan McDermott"defsplit_into_first_and_last_name()->None:# O uso da palavra-chave global aqui está mudando o significado da# seguinte linha. Esta função agora está alterando o nível do módulo# estado e introduzindo um efeito colateral!globalfullnamefullname=fullname.split()split_into_first_and_last_name()# MyPy irá detectar o problema,  'Incompatible types in# assignment: (expression has type "List[str]", variable has type "str")'print(fullname)# ["Ryan", "McDermott"]# OK. Funcionou da primeira vez, mas o que acontecerá se chamarmos de# funcionar de novo?

Bom:

fromtypingimportList,AnyStrdefsplit_into_first_and_last_name(name:AnyStr)->List[AnyStr]:returnname.split()fullname="Ryan McDermott"name,surname=split_into_first_and_last_name(fullname)print(name,surname)# => Ryan McDermott

Muito bom

fromtypingimportTextfromdataclassesimportdataclass@dataclassclassPerson:name:Text@propertydefname_as_first_and_last(self)->list:returnself.name.split()# A razão pela qual criamos instâncias de classes é para gerenciar o estado!person=Person("Ryan McDermott")print(person.name)# => "Ryan McDermott"print(person.name_as_first_and_last)# => ["Ryan", "McDermott"]

⬆ back to top

Objetos e Estruturas de Dados

Coming soon

⬆ back to top

Classes

Princípio da Responsabilidade Única (SRP)

Princípio do Aberto/Fechado (OCP)

Princípio de Substituição de Liskov (LSP)

Princípio da Segregação de Interface (ISP)

Princípio da Inversão de Dependência (DIP)

Coming soon

⬆ back to top

Não se repita (DRY)

Mais informações sobre o príncipioDRY.

Como programador, você deve evitar código duplicado. A duplicação é ruim porque isso significaque há mais de um lugar para alterar algo, se precisar mudar alguma lógica

Imagine que você é dono de um restaurante e você toma conta do seu estoque: todos os seus tomates, cebolas, alhos, temperos, etc. Se você tem multiplas listas onde guarda estas informações, então você terá que atualizar todas elas quando servir um prato que tenha tomates. Se você tivesse apenas uma lista, teria apenas um lugar para atualizar!

Frequentemente, você possui código duplicado porque você tem duas ou mais coisas levemente diferentes, que possuem muito em comum, mas suas diferenças lhe forçam a ter mais duas ou três funções que fazem muito das mesmas coisas. Remover código duplicado significa criar uma abstração que seja capaz de lidar com este conjunto de coisas diferentes com apenas uma função/módulo/classe.

Conseguir a abstração correta é crítico, por isso que você deveria seguir os princípios SOLID descritos na seção Classes. Abstrações ruins podem ser piores do que código duplicado, então tome cuidado! Dito isto, se você puder fazer uma boa abstração, faça-a! Não repita a si mesmo, caso contrário você se pegará atualizando muitos lugares toda vez que precisar mudar qualquer coisinha.

Ruim:

fromtypingimportList,Text,Dictfromdataclassesimportdataclass@dataclassclassDeveloper:def__init__(self,experience:float,github_link:Text)->None:self._experience=experienceself._github_link=github_link@propertydefexperience(self)->float:returnself._experience@propertydefgithub_link(self)->Text:returnself._github_link@dataclassclassManager:def__init__(self,experience:float,github_link:Text)->None:self._experience=experienceself._github_link=github_link@propertydefexperience(self)->float:returnself._experience@propertydefgithub_link(self)->Text:returnself._github_linkdefget_developer_list(developers:List[Developer])->List[Dict]:developers_list= []fordeveloperindevelopers:developers_list.append({'experience' :developer.experience,'github_link' :developer.github_link            })returndevelopers_listdefget_manager_list(managers:List[Manager])->List[Dict]:managers_list= []formanagerinmanagers:managers_list.append({'experience' :manager.experience,'github_link' :manager.github_link            })returnmanagers_list## create list objects of developerscompany_developers= [Developer(experience=2.5,github_link='https://github.com/1'),Developer(experience=1.5,github_link='https://github.com/2')]company_developers_list=get_developer_list(developers=company_developers)## create list objects of managerscompany_managers= [Manager(experience=4.5,github_link='https://github.com/3'),Manager(experience=5.7,github_link='https://github.com/4')]company_managers_list=get_manager_list(managers=company_managers)

Bom:

fromtypingimportList,Text,Dictfromdataclassesimportdataclass@dataclassclassEmployee:def__init__(self,experience:float,github_link:Text)->None:self._experience=experienceself._github_link=github_link@propertydefexperience(self)->float:returnself._experience@propertydefgithub_link(self)->Text:returnself._github_linkdefget_employee_list(employees:List[Employee])->List[Dict]:employees_list= []foremployeeinemployees:employees_list.append({'experience' :employee.experience,'github_link' :employee.github_link            })returnemployees_list## create list objects of developerscompany_developers= [Employee(experience=2.5,github_link='https://github.com/1'),Employee(experience=1.5,github_link='https://github.com/2')]company_developers_list=get_employee_list(employees=company_developers)## create list objects of managerscompany_managers= [Employee(experience=4.5,github_link='https://github.com/3'),Employee(experience=5.7,github_link='https://github.com/4')]company_managers_list=get_employee_list(employees=company_managers)

⬆ back to top

About

🛁 Conceitos do Clean Code adaptados para Python

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python97.7%
  • Makefile2.3%

[8]ページ先頭

©2009-2025 Movatter.jp