编码最佳实践,第一章:函数。第 1 章:函数

2025-05-25

编码最佳实践,第一章:功能。

第一章:功能

我的目的并非要告诉你编写代码的最佳方法,有些程序员有他们自己的方法,而且效果很好。我只是想与大家分享我认为的最佳实践,以及如何改进代码的准备度、结构、组件、含义、调试、重构等各个方面。

第一章:功能

做一件事。

我想从最基本的规则开始,在我看来,这也是最佳实践:一桩事规则。它会让我们的生活更轻松,尝试创建你的函数来做一件事。

编写只做一件事的函数可能非常困难,但这应该是我们日常工作的最终目标。为什么?让我们来看看这个

def create_user(email, password):
    try:
        user = User.objects.get(email=email)
    catch UserDoesNotExist:
        user = User.objects.create(
            email=email,
            password=password
        )
        auth_valid = authenticator(
            user=user,
            password=password
        )
        if auth_valid:
            user.last_login = datetime.now()
            user.save()
            msg_content = '<h2>Welcome {email}</h2>'.format(
                email=email
            ) 
            message = MIMEText(
                msg_content,
                'html'
            )
            message.update({
                'From': 'Sender Name <sender@server>',
                'To': email,
                'Cc': 'Receiver2 Name <receiver2@server>',
                'Subject': 'Account created'
            })
            msg_full = message.as_string()
            server = smtplib.SMTP('smtp.gmail.com:587')
            server.starttls()
            server.login(
                'sender@server.com',
                'senderpassword'
            )
            server.sendmail(
                'sender@server.com',
                [email],
                msg_full
            )
            server.quit()
            return user
Enter fullscreen mode Exit fullscreen mode

这个函数起什么作用?

  • 如果用户不存在,它会创建一个新用户。
  • 它创建一个 HTML 模板。
  • 它初始化一个 SMTP 连接器。
  • 它发送一封电子邮件。

为什么它是错的?

  1. 正在做多件事。
  2. 此功能的单元测试将会非常庞大​​和混乱,为什么?例如,测试逻辑应该同时考虑身份验证和欢迎电子邮件流程,而这两个流程应该分开测试。
  3. 引入错误更容易,假设我们想要更改欢迎电子邮件,我们需要触及我们的功能,因此,为了修复流程 B 而对流程 A 进行更改是没有意义的。
  4. 如果我们尝试做多件事,它会增加我们的功能行数,从而失去准备。
  5. 要做的事情越多,调试就越困难。

那么,我们如何才能修复或减少上面列出的问题呢?让我们重构代码。

def signup(email, password):
    new_user = create_user(
        email,
        password
    )
    authenticate(user)
    send_welcome_email(email)

def create_user(email, password):
    try:
        user = User.objects.create(
            email=email,
            password=password
        )
        return user
    except UserAlreadyExists:
        raise Exception('User exists')

def authenticate(user, password):
    auth_valid = authenticator(
        user=user,
        password=password
    )
    if auth_valid:
        user.last_login = datetime.now()
        user.save()

def send_welcome_email(email):
    email_msg = create_welcome_email_msg(email)
    send_email_to(
        email,
        email_msg
    )

def create_welcome_email_msg(email):
    msg_content = '<h2>Welcome {email}</h2>'.format(
        email=email
    ) 
    message = MIMEText(
        msg_content,
        'html'
    )
    message.update({
        'From': 'Sender Name <sender@server>',
        'To': email,
        'Cc': 'Receiver2 Name <receiver2@server>',
        'Subject': 'Account created'
    })
    msg_full = message.as_string()
    return msg_full

def send_email_to(email, email_msg):
    server = smtplib.SMTP('smtp.gmail.com:587')
    server.starttls()
    server.login(
        'sender@server.com',
        'senderpassword'
    )
    server.sendmail(
        'sender@server.com',
        [email],
        msg_full
    )
    server.quit()
Enter fullscreen mode Exit fullscreen mode

我们做了什么?

好多了,对吧?

  • 我们将create_user函数拆分成更小的函数,这样我们就可以分离逻辑并添加不同的新抽象级别signup function
  • 我们也对其余功能应用了同样的原则(一物一码原则)。因此,准备工作得到了提升,您可以轻松进行单元测试,并且现在可以在不影响用户创建流程的情况下更改欢迎电子邮件。

专业提示
那么,我们如何知道我们的函数不只做一件事呢?最简单的方法是,如果您可以从函数中提取一些逻辑,并使用不同的逻辑名称将其放入新的函数中,那么很可能您的函数正在做不止一件事。

抽象级别

为了确保我们的函数正在做一件事,我们应该在所有函数中保持相同的抽象级别。


在我们之前的示例(代码预览 1)中,我们可以发现不同级别的抽象,我们使用高级函数对用户进行身份验证,authenticator(user=user,password=password)但同时,我们也从字符串创建了 HTML 模板msg_content = '<h2>Welcome {email}</h2>'.format(email=email)。这种函数定义应该避免。

基本上,我们在创建函数时就应该保持一致。这也有助于读者理解我们的函数是一个高级概念还是一个详细的过程。

函数内的部分

另一种判断函数是否执行多项操作的方法,是将其拆分成不同的部分,并将多行代码分组

def foo():
    # initialization
    # ...... several lines here
    # logic 1
    # ....... several lines here
    # Prepare return value
    # ....... several lines here 
Enter fullscreen mode Exit fullscreen mode

在这个例子中,我们在同一级别上做了多件事,为了避免这种情况,你可以提取其他函数中的每个部分,然后将它们一起使用,当然,这取决于你的函数的抽象级别。

识别级别

我曾经尝试理解超过两级缩进的函数。如果你也遇到过这种情况,那么我们可以一致认为,
在最坏的情况下,最大缩进级别应该为 2。

如果你的函数中有很多块if/else嵌套在其他if/else块中,那么你的函数肯定不是在做“一件事”。我们来看一个例子。

def buy_concert_ticket(user, ticket):

    ticket_price = ticket.price
    user_age = user.age

    if user.age >= 18:
        user_money = user.money
        if user_money >= ticket_price:
            seats = stadium.seats
            seat_available = None
            for seat in seats:
                if seat.is_available():
                    seat_available = seat
                    break

            if seat_available: 
                seat.owner = user
                user.money -= ticket.price
                print("Congratulations, you have a seat")
            else:
                print("There is not available seat")

        else:
            print("Sorry you don't have enough balance")
    else:
        print("You are not allowed to buy a ticket")
Enter fullscreen mode Exit fullscreen mode

因此,这些函数允许用户购买机票,但在完成购买之前,它会验证一些条件。
此功能不符合我们之前的规则:

  • 它有超过 2 个级别的缩进/块。
  • 它的作用不止一个。
  • 它有几个部分。
  • 由于嵌套的 if/else 块,它很难阅读。

如何解决这个问题?
1.- 我们应该尝试通过删除嵌套的 if/else 块来降低缩进级别。

def buy_concert_ticket(user, ticket):

    ticket_price = ticket.price
    user_age = user.age
    user_money = user.money

    if not user.age >= 18:
        print("You are not allowed to buy a ticket")
        return 

    if not user_momey >= ticket_price:
        print("Sorry you don't have enough balance")
        return

    for seat in steats:
        if seat.is_available():
            seat.owner = user
            user.money -= ticket.price
            print("Congratulations, you have a seat")
            return

    print("There is no available seat")
    return
Enter fullscreen mode Exit fullscreen mode

你看,我们能够删除所有的if/else blocks,所以我们的最大缩进级别现在为 2。现在,该功能
更加准备就绪。

专业提示:如果您尝试使用解释的规则来制作更简单的函数,那么您将永远不需要else再次使用函数逻辑目的。

2.- 让我们确定函数的功能,并将它们提取到单独的函数中。重构后的代码应该如下所示。

def buy_concert_ticket(user, ticket):

    if not user_has_legal_age(user):
        print("You are not allowed to buy a ticket")
        return

    if not user_has_enough_balance(user, ticket):
        print("Sorry you don't have enough balance")
        return

    available_seat = get_available_seat()

    if not available_seat:
        print("There is not available seat")
        return

    buy_seat(user, available_seat)

    return

def user_has_legal_age(user):
    user_age = user.age

    if not user.age >= 18:
        return False

    return True

def user_has_enough_balance(user, ticket):
    user_money = user.money
    ticket_price = ticket.price

    if user_momey >= ticket_price:
        return True

    return False

def get_available_seat():
    seats = stadium.seats
    for seat in seats:
        if seat.is_available():
            return seat

def buy_seat(user, seat):
    seat.owner = user
    user.money -= ticket.price
    print("Congratulations, you have a seat")
    return

Enter fullscreen mode Exit fullscreen mode

我们能够从现有功能中提取四个新功能,这意味着我们尝试做太多事情。

现在,我们的主函数buy_concert_ticket可以用“自然语言”来理解了。当然,我使用了准确的名称(我们稍后会讨论这个问题),但代码行数少,而且只增加了一级缩进,对实现这个效果很有帮助。

3.- 你觉得我们不能把函数做得更小一些吗?或者减少部分?我们可以,我们再看看。

def buy_concert_ticket(user, ticket):

    if not user_can_buy_a_ticket(user, ticket):
        return

    buy_available_seat(user)

    return

def user_can_buy_a_ticket(user, ticket):

    if not user_has_legal_age(user, ticket):
        print("You are not allowed to buy a ticket")
        return False

    if not user_has_enough_balance(user, ticket):
        print("Sorry you don't have enough balance")
        return False

    return True

def user_has_legal_age(user):
    user_age = user.age

    if not user.age >= 18:
        return False

    return True

def user_has_enough_balance(user, ticket):
    user_money = user.money
    ticket_price = ticket.price

    if user_momey >= ticket_price:
        return True

    return False

def buy_available_seat(user):
    available_seat = get_available_seat()

    if not available_seat:
        print("There is not available seat")

    buy_seat(user, available_seat)
    return

def get_available_seat():
    seats = stadium.seats
    for seat in seats:
        if seat.is_available():
            return seat


def buy_seat(user, seat):
    seat.owner = user
    user.money -= ticket.price
    print("Congratulations, you have a seat")
    return
Enter fullscreen mode Exit fullscreen mode

我们可以稍微精简一下主函数,现在我们把它从 21 行精简到了 4 行。我们只是运用了同样的原则,
尝试抽象逻辑并识别代码段/块,以便创建新函数。这会成为一个迭代的过程。

那么,我们得到了什么?

  • 任何尝试阅读我们的功能的人只需几秒钟就能明白我们想要做什么。
  • 您可以像阅读“自上而下的故事”一样阅读整个过程。
  • 现在,实施单元测试会更容易,因为我们将购票过程的每个步骤都视为一个单独的功能。
  • 如果我们想在流程中添加额外的要求,那么确定要更改的功能就足够了,例如:为买家添加另一个限制,我们只需要更改user_can_buy_ticket()功能内的逻辑,而无需触及其他任何东西。

注意:我知道这样做很耗时,但相信我,即使由于截止日期等原因无法进行多次迭代,也请尝试至少进行一次。第一次迭代总会带来良好的结果。

就这样。在下一章中,我将进一步讨论如何定义函数参数以提高准备度,并提供其他技巧。

如果您喜欢我的内容或者它对您有帮助,您可以给我买杯咖啡来激励我写更多内容
最初发布在我的个人博客中

文章来源:https://dev.to/levivm/coding-best-practices-chapter-one-functions-4n15
PREV
使用 NodeJS 通过 Google Cloud Functions API REST 创建无服务器 REST API 的初学者指南,使用 Google Cloud Functions(无服务器)
NEXT
你值得成为一名软件开发人员