Aliaksei Karatynski#8
Conversation
| import argparse | ||
|
|
||
|
|
||
| def arg_parser(): |
There was a problem hiding this comment.
Согласно негласным соглашениям хорошим тоном считается давать функциям названия, выражающие действие (глагол). Название parse_args подошло бы лучше.
Кроме того, объем и сложность логики данной функции очень малы для вынесения в отдельный модуль. Обычно такую функцию называют _parse_args и располагают в модуле, являющемся точкой входа приложения.
| """ | ||
| Import modules | ||
| """ | ||
| sys.path.insert(0, os.path.abspath(os.path.curdir)) |
|
|
||
| def split_operands(merged_operand): | ||
| """ | ||
| Split operands |
There was a problem hiding this comment.
Док стринг повторяет название функции, что бессмысленно. Он может нести больше полезной информации, либо докстринг можно опустить, чтобы не захламлять код.
если есть желание уточнить принимаемый/возвращаемый типы, можно использовать тайп хинты https://docs.python.org/3/library/typing.html
| ) | ||
|
|
||
| PRIORITIES = { | ||
| 1: ['<', '>', '<=', '>=', '==', '!='], |
There was a problem hiding this comment.
Вместо списка для констант лучше использовать кортежи. Таким образом мы подчеркиваем, что данное перечисление не планируется изменять и используем меньше памяти.
| token_end_position = -1 | ||
| insert_positions = [] | ||
| expression = expression.replace(' ', '') | ||
| expression = re.sub(r'\)\(', ')*(', expression) |
There was a problem hiding this comment.
Регулярные выражения лучше выносить в отдельные переменные, чтобы программист, который читает код, смог получить дополнительную информацию о происходящем из названия переменной. Чтение названия переменной занимает намного меньше времени, чем попытка осознать смысл регулярного выражения.
| expression = list(expression) | ||
| for index in insert_positions: | ||
| expression.insert(index, '*') | ||
| return ''.join(expression) |
There was a problem hiding this comment.
Судя по всему в этой функции выполняется неявная токенизация, подстановка знаков умножения, а затем обратный перевод в строку. Зачем делать одну и ту же работу два раза?
| if module == builtins and attribute not in BUILTINS_FUNCS: | ||
| continue | ||
| return module | ||
| return |
| :param expression | ||
| :param token_end_position | ||
| :param module | ||
| :return: the constant or result of the function and |
There was a problem hiding this comment.
Функция должна обладать единым четким и понятным интерфейсом. Результат работы функции должен интерпретироваться единообразно.
| @@ -0,0 +1,487 @@ | |||
| """ | |||
There was a problem hiding this comment.
Код нуждается в декомпозиции. Например разделить модуль calculate на несколько маленьких модулей или инкапсулировать функциональность в несколько разных классах, некоторые большие функции разделить на несколько маленьких, где это возможно
| answer = do_calculation(arguments.EXPRESSION, arguments.MODULE) | ||
| print(answer) | ||
| if str(answer).startswith('ERROR'): | ||
| sys.exit(-1) |
There was a problem hiding this comment.
Почему -1? Не соответствует конвенции UNIX описывающей возвращаемые значения программы (от 0 до 255)
| return operator == '^' | ||
|
|
||
|
|
||
| def check_is_callable(attribute, module): |
There was a problem hiding this comment.
В чем смысл этой функции? Она используется в одном месте в бизнес логике приложения (исключая юнит тесты) и не содержит какую-либо существенную логику.
| @@ -0,0 +1,14 @@ | |||
| """ | |||
| This module allows to installs the application | |||
There was a problem hiding this comment.
Слишком очевидно чтобы быть тут.
| import sys | ||
| import operator as op | ||
| from .calculator_helper import PycalcError | ||
| from .calculator_helper import ( |
There was a problem hiding this comment.
В случаях, когда импортируется множество сущностей из одного и того же модуля, принято распологать их по одному на строку, для повышения читаемости.
from .calculator_helper import (
check_is_number,
check_may_unary_operator,
...
check_brackets_balance,
)| import os | ||
| import re | ||
| import sys | ||
| import operator as op |
There was a problem hiding this comment.
Сомнительная выгода с точки зрения объема кода, но понижение читаемости существенное.
Кроме того в твоем коде активно используется имя operator, что может запутать программиста, который может ожидать от этого имени использование стандартной библиотеки
| """ | ||
| arguments = args.arg_parser() | ||
|
|
||
| answer = do_calculation(arguments.EXPRESSION, arguments.MODULE) |
There was a problem hiding this comment.
Вместо возвращения объекта ошибки из функции do_calculation логичнее отлавливать исключения на самом высоком уровне:
try:
print(do_calculation(arguments.EXPRESSION, arguments.MODULE))
except PycalcError as e:
print(e)
except Exception as e:
print("ERROR: {}".format(e))| return True | ||
|
|
||
|
|
||
| def check_valid_spaces(expression): |
There was a problem hiding this comment.
Название и описание функции не соответствует ее назначению. Функция явно в целом проверяет валидность выражения, а не только пробелы.
| float(string) | ||
| return True | ||
| except ValueError: | ||
| return |
There was a problem hiding this comment.
Лучше в данном случае вернуть False
| check_valid_spaces(expression) | ||
|
|
||
|
|
||
| def update_operands(expression, operands, index): |
There was a problem hiding this comment.
Ни из названия функции, ни из ее описания не ясно что она делает. Кроме обновления операндов она считает некий token_end_position. Это явно указывет на ошибки, совершенные при декомпозиции (разбиении программы на отдельные независимые сущности). То же самое для функции update_operators (строка 401).
| """ | ||
| for operator in operators: | ||
| if get_priority(operator) > 1: | ||
| return |
There was a problem hiding this comment.
Несоответствие с докстрингом, в данном случае функция возвращает None
| condition_b = get_priority(operators[-1]) >= get_priority(operator) | ||
| condition_c = check_right_associativity(operator) | ||
| condition_d = get_priority(operators[-1]) > get_priority(operator) | ||
| condition = condition_a and condition_b or condition_c and condition_d |
There was a problem hiding this comment.
Плохо. Такие блоки кода очень сложно понять человеку, не знакомому с проектом. Если бы декомпозиция была проведена грамотно, в функции не было бы такого количества условий.
|
В целом в проекте прослеживается одна и та же проблема: декомпозиция (разбиение логики проекта на отдельные независимые сущности) проведена не лучшим способом. Из-за этого появились функции, выполняющие сразу несколько обязанностей. Из-за этого становится сложно подобрать функции верное и понятное название, сложно выбрать простой и четкий интерфейс. Код становится запутанным. |
No description provided.