如何重构以避免传递函数?

How do I refactor to avoid passing functions?

我目前有代码使用 pandas DataFrames 从多个来源获取和操作数据。目的是让用户创建 class 的实例(称之为 dbase),它提供方法来执行诸如从 API 查询中获取和存储数据等操作。我通过允许用户定义他们自己的函数来格式化 dbase 中的值来实现这一点,但我发现我倾向于通过其他几个函数传递这些用户定义的函数,这会让人感到困惑。我认为对于知道自己在做什么的人来说,这一定是一个明显的错误,但我还没有想出更好的方法来让用户控制数据。

API 查询是目前最糟糕的例子。假设我想从服务器获取名称。现在我做了类似下面的事情,其中​​用于转换名称的用户定义函数在调用之前在其他三个函数之间传递。

# file with code for api interaction
def submitter(this_query, dbase, name_mangler):
    new_data = api.submit(this_query)
    new_dbase_entry = name_mangler(new_data)
    # in reality there is much more complicated data transformation here
    dbase.update(new_dbase_entry)

def query_api(dbase, meta, name_mangler):
    queries = make_query_strings(dbase, meta)
    # using pandas.DataFrame.apply() here to avoid a for loop
    queries.apply(lambda x: submitter(x, dbase))
# other file with class definition
from api_code import query_api

class dbase():

    __init__():
        self.df = pandas.DataFrame()
        # data gets moved around between more than one data
        # structure in this class, I'm just using a single
        # dataframe as a minimal example

    def get_remote_data(self, meta, name_mangler):
        # in reality there is code here to handle multiple
        # cases here rather than a trivial wrapper for another
        # function
        query_api(self, meta, name_mangler)

    def update(self, new_data):
        # do consistency checks
        # possibly write new dbase entry

然后用户会做这样的事情

import dbase

def custom_mangler(name):
    # User determines how to store the name in dbase
    # for instance this could take "Grace Hopper" to "hopper"
    return(mangled_name)

my_dbase = dbase.dbase()
# meta defines what needs to be queried and how the remote
# data should get processed into dbase
meta = {stuff} 
my_dbase.get_remote_data(meta, custom_mangler)

我发现在这里很难遵循我自己的代码,因为函数的定义可能与调用它们的第一点大相径庭。我应该如何重构来解决这个问题? (这种方法是否由于其他原因违反了公认的编码模式?)

从您发布的内容中推断上下文有点困难,所以请对此持保留态度。一般概念仍然适用。另请查看 https://codereview.stackexchange.com/,因为这个问题可能更适合该网站。

我想到了两件事。

  1. 试着给你的 functions/classes/variables 更好的名字
  2. 考虑正交性

好名字

从用户的角度考虑这看起来如何。 dbase 不是模块或 class 的描述性很强的名称。 meta 根本没有告诉我字典应该包含什么。 mangler 告诉我字符串已更改,但没有说明字符串的来源或应如何更改。

好名字很难,但花时间让它们深思熟虑是值得的。它总是在描述性和过于冗长之间进行权衡。如果您想不出一个既能给出明确含义又不会占用太多空间的名称 space,那么请考虑您的 API 是否过于复杂。始终从最终用户的角度以及未来的程序员的角度考虑名称,他们将成为 reading/maintaining 您的代码。

正交性

遵循 Unix 的“做一件事并做好它”的口头禅,有时 API 如果我们将不同的任务分配给不同的函数而不是让一个函数完成所有事情,那么有时 API 会更简单、更灵活.

在写代码的时候,我认为“这个函数最少需要做什么才有用”。

在你的例子中

my_dbase.get_remote_data(meta, custom_mangler)

get_remote_data不仅获取数据,而且处理数据。作为用户,这可能会造成混淆。此函数的幕后发生了很多从函数名称中看不出来的事情。

为此进行单独的函数调用可能更合适。假设您要查询有关温度和降雨量的天气服务器。

london_weather_data = weatheraggrigator.WeatherAggrigator()
reports = london_weather_data.fetch_weather_reports(sources=[server_a, server_b])
london_weather_data.process_reports(reports, short_name_formatter)

是的,打字的时间变长了,但作为用户,这是一个很大的进步,因为我知道自己得到了什么。

最终您需要决定在哪里拆分任务。以上内容可能对您的应用没有意义。