提示与技巧

避免代码重复

尽可能避免复制粘贴同一段代码到多个地方是很好的习惯。尽管有时需要一些思考才能明白如何避免复制粘贴代码,但是将代码只重复一次通常会在你需要更改设计或者修复错误时为你节省很多时间和精力。

下面是一些实现代码复用的技巧。

不要将你的应用复制多份

你应当尽可能避免复制你的应用文件夹来创建一个有细微差别的版本,因为此时重复代码很难维护。

If you need multiple rounds, set num_rounds. If you need slightly different versions (e.g. different treatments), then you should use the techniques described in 实验组(Treatments), such as making 2 session configs that have a different 'treatment' parameter, and then checking for session.config['treatment'] in your app’s code.

如何创建多个字段

假定你的应用中有很多几乎完全一样的字段,例如:

class Player(BasePlayer):

    f1 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f2 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f3 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f4 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f5 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f6 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f7 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f8 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f9 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )
    f10 = models.IntegerField(
        choices=[-1, 0, 1], widget=widgets.RadioSelect,
        blank=True, initial=0
    )

    # etc...

这太复杂了,你应当找到一个能将其简化的方法。

这些字段都显示在单独的页面上吗?如果是的,考虑将其转换为一个仅有一个字段的10轮游戏。查看 real effort 示例游戏,这个例子说明了仅包含1页循环多轮的游戏,如何在每一轮显示不同的问题。

如果上述均不可行,你可以通过定义一个返回字段的函数的方式减少重复代码的数量:

def make_field(label):
    return models.IntegerField(
        choices=[1,2,3,4,5],
        label=label,
        widget=widgets.RadioSelect,
    )

class Player(BasePlayer):

    q1 = make_field('I am quick to understand things.')
    q2 = make_field('I use difficult words.')
    q3 = make_field('I am full of ideas.')
    q4 = make_field('I have excellent ideas.')

通过使用多轮游戏避免代码重复

如果你有很多几乎完全一样的页面,考虑仅保留一个页面并将游戏重复多轮。你的代码可被简化的一个标志是它看上去像下面这样:

# [pages 1 through 7....]

class Decision8(Page):
    form_model = 'player'
    form_fields = ['decision8']

class Decision9(Page):
    form_model = 'player'
    form_fields = ['decision9']

# etc...

参考 quizreal effort 示例游戏,这个例子说明了仅包含1页循环多轮的游戏,如何在每一轮显示不同的问题。

提高代码性能

你应当避免不必要的 get_players(), get_player_by_id(), in_*_rounds(), get_others_in_group() 或者任何返回一个玩家或者玩家列表的函数的调用。这些方法都需要数据库查询,而数据库查询是很慢的。

例如,下面的代码有冗余的查询,对同一玩家进行了5次数据库查询:

def vars_for_template(player):
    return dict(
        a=player.in_round(1).a,
        b=player.in_round(1).b,
        c=player.in_round(1).c,
        d=player.in_round(1).d,
        e=player.in_round(1).e
    )

这应当被简化为:

def vars_for_template(player):
    round_1_player = player.in_round(1)
    return dict(
        a=round_1_player.a,
        b=round_1_player.b,
        c=round_1_player.c,
        d=round_1_player.d,
        e=round_1_player.e
    )

作为额外的好处,这通常使得代码更具有可读性。

使用BooleanField而不是StringField,如果可能的话

许多 StringFields 应当被转化为 BooleanFields,尤其是其仅有2个不同的值时。

假定你有一个字段叫做 treatment:

treatment = models.StringField()

假定 treatment 有4个不同的可能值:

  • high_income_high_tax
  • high_income_low_tax
  • low_income_high_tax
  • low_income_low_tax

在你的页面中,你可能会这样使用它:

class HighIncome(Page):
    @staticmethod
    def is_displayed(player):
        return player.treatment == 'high_income_high_tax' or player.treatment == 'high_income_low_tax'

class HighTax(Page):
    @staticmethod
    def is_displayed(player):
        return player.treatment == 'high_income_high_tax' or player.treatment == 'low_income_high_tax'

将其化为两个单独的BooleanFields是很好的改进:

high_income = models.BooleanField()
high_tax = models.BooleanField()

那么你的页面代码可被简化为:

class HighIncome(Page):
    @staticmethod
    def is_displayed(player):
        return player.high_income

class HighTax(Page):
    @staticmethod
    def is_displayed(player):
        return player.high_tax

避免重复的验证方法

如果你有多个重复的 FIELD_error_message 方法,你可以使用一个 error_message 来替换它。例如:

def quiz1_error_message(player, value):
    if value != 42:
        return 'Wrong answer'

def quiz2_error_message(player, value):
    if value != 'Ottawa':
        return 'Wrong answer'

def quiz3_error_message(player, value):
    if value != 3.14:
        return 'Wrong answer'

def quiz4_error_message(player, value):
    if value != 'George Washington':
        return 'Wrong answer'

你可以在你的页面中(不是Player类中)定义一个方法来替换:

def error_message(player, values):
    solutions = dict(
        quiz1=42,
        quiz2='Ottawa',
        quiz3='3.14',
        quiz4='George Washington'
    )

    error_messages = dict()

    for field_name in solutions:
        if values[field_name] != solutions[field_name]:
            error_messages[field_name] = 'Wrong answer'

    return error_messages

(通常 error_message 被用来返回某一错误信息字符串,但你也可以返回一个字典。)