我如何学习任何类型的新技术(作为高级开发人员)

2025-05-25

我如何学习任何类型的新技术(作为高级开发人员)

最近,我有一项任务,需要学习一种我从未使用过或见过相关内容的新工具,我想:为什么不写下我是如何学习它的呢?

这篇文章会教你从不同的角度学习事物。虽然方法可能不是最好的,但你可以复用这里提到的一些概念。

目录

1. 序言

图片描述

最近我和朋友们用 PHP 和 Laravel 启动了一个新项目(又一个糟糕的 SaaS 应用程序)。项目本身并不重要,重要的是技术。为什么呢?因为我们没有足够的钱来购买自托管的基础设施,也没有钱购买好的付费工具……所以这很重要。

所以,如果我们要构建某种集成功能,至少需要有一个免费层级供用户使用,而不是实现起来像地狱一样。在思考这个问题时,我们偶然发现了一个问题:我们该如何进行用户跟踪/指标

我的目标是展示SaaS 用户每页的分析数据。普通开发者可能会想:我们用Google AnalyticsGoogle Tag Manager就可以了,对吧?好吧……让我告诉你 Chester 曾经说过的一句话:

我如此努力,也如此坚持。但最终,一切都变得无关紧要了——查斯特·贝宁顿

哥们,我一直讨厌 Google UI。但这次不是因为工作,而是为了我自己,我没时间浪费。所以,我花了 4 个小时试图……在我的项目上进行基本的设置,并了解如何创建一个简单的标签?结果,我浪费了我的时间,因为什么也没用。

比如,为什么所有谷歌产品的界面都需要博士学位才​​能使用?到处都是信息!

就在那时,我的朋友告诉我PostHog,一个开源分析平台,可以让你通过自托管或云选项追踪用户行为(并且在实现方面胜过 GTM/GA4)。但与此同时,我希望你能坚持读完这篇文章,因为我花了几个小时和几行代码来完成这项研究。

2. 基础知识

图片描述

好吧,这是最显而易见的事情,但必须说的是:当你尝试一个新工具时,你必须充分体验它的各个方面。我知道我们是开发人员,但产品的用户界面正在试图告诉你一些事情

2.1 打破常规,直至成功

PostHog 的设置过程花费的时间少了很多,这绝对是个优点。然而,它的 UI 界面太多了,我第一个想法就是尝试把所有东西都点进去,然后以创纪录的速度中断我手头的项目

跟我一起想一想:如果您只是因为身处云或“生产”环境而害怕使用平台/产品,那么最终您将学不到任何东西。

完成此步骤后,我只是删除了那个混乱的项目,然后重新开始了一个。不过此时,我已经知道 UI 是如何运作的了。

免责声明:请勿在您公司的云帐户上执行此操作,请创建一个并爆炸您自己的环境!

2.2 但是...这并不那么明显

图片描述

好吧……我得坦白地告诉你,有时候把所有功能都拆开也未必能解决问题,因为根据你使用的功能,这甚至不是一个问题。PostHog 有“TrendsQuery”、“RetentionQuery”、“Web Vitals”之类的功能,以及针对每种查询类型都设计了一个非常精美的 UI 查询生成器。真的很棒!但是……我没能马上用上。

由于我患有一种叫做注意力缺陷多动症(ADHD)的奇怪疾病,当我看到屏幕上出现带有条件组件的极简 UI时,我就会惊慌失措。但这不是放弃的理由,对吧?

这部分并非对产品的批评,而是我自己的吐槽。数据科学是一个充满无限条件规则的领域,你掌握的信息越多,结果就越准确,而我正迈出学习它的第一步。

3. 文档

图片描述

当然,还有文档!我以前怎么没用过呢?我的意思是,这里是开发者的天地,对吧?没错!但我们是使用产品的开发者,所以第一步是必须的。

我通常不看教程,尽管我知道每个人都应该看。我就是不喜欢看教程。对我来说,好的 API 参考资料必须告诉你一切,但在我看来,什么才是“一切”呢?

  • “可索引”标题:位于目录下,这很有帮助。
  • 精致的描述:告诉您在使用时需要了解的一切。
  • 实现示例:使用 PHP、JS、Go、Rust 和 cURL 等语言
  • 预期响应:根据响应状态(200、401、422、500)提供哪些具有“真实数据”的有效载荷。
  • 实现参考:在这里您可以找到实际的代码来阅读和了解客户端/服务器端的工作方式。
  • 所需范围:通常您应该有端点的范围。

大多数情况下,你会有很多像我上面列出的元素,但这取决于你使用的 API。在我的 PostHog特定实现中,挑战在于/query/create端点具有多态请求和响应,因此那里没有响应负载。

因此,我们开始深入研究真正的代码,因为我们需要了解这些端点如何与实际实现协同工作。

为了避免这种情况,我可以只写下一个HogQL 查询,但学习它(对我来说)需要花费更多的时间。

4.源代码

图片描述

我们现在知道我们想要什么了,对吧?我们想了解每种类型的请求会发送什么有效载荷。就我而言,我想了解PostHog 上的留存率和趋势查询是如何运作的。而且由于该产品是开源的,我可以直接从 GitHub 上阅读它!

前端用TypeScript编写,后端用Python编写。既然我是一名后端开发人员,你会认为我会选择 Python 实现来深入研究,对吧?其实,出于很多原因,我讨厌 Python。此外,TypeScript 还提供了类型安全性,可以作为代码搜索的参考。

在TypeScript 源代码我找到了客户端所需的一切的实现,但看到它之后我直接进入了兔子洞,你就会明白为什么。

看一下这个片段(参考)

export interface TrendsQuery extends InsightsQueryBase<TrendsQueryResponse> {
    kind: NodeKind.TrendsQuery
    /**
     * Granularity of the response. Can be one of `hour`, `day`, `week` or `month`
     *
     * @default day
     */
    interval?: IntervalType
    /** Events and actions to include */
    series: AnyEntityNode[]
    /** Properties specific to the trends insight */
    trendsFilter?: TrendsFilter
    /** Breakdown of the events and actions */
    breakdownFilter?: BreakdownFilter
}
Enter fullscreen mode Exit fullscreen mode

这里我们可以假设几件事:

  • 有一个继承的InsightsQueryBase,因此其他查询具有相同的基础。
  • TrendsQueryResponse可用并且必须遵循某种模式,因此我们可以稍后检查它
  • 查询“关注”(您可以将其视为过滤器)并不那么明确,需要进一步研究。

首先,我们需要了解基础是什么,并寻找InsightsQuery 关注点


/** Base class for insight query nodes. Should not be used directly. */
export interface InsightsQueryBase<R extends AnalyticsQueryResponseBase<any>> extends Node<R> {
    /** Date range for the query */
    dateRange?: DateRange
    /**
     * Exclude internal and test users by applying the respective filters
     *
     * @default false
     */
    filterTestAccounts?: boolean
    /**
     * Property filters for all series
     *
     * @default []
     */
    properties?: AnyPropertyFilter[] | PropertyGroupFilter
    /**
     * Groups aggregation
     */
    aggregation_group_type_index?: integer | null
    /** Sampling rate */
    samplingFactor?: number | null
    /** Colors used in the insight's visualization */
    dataColorTheme?: number | null
    /** Modifiers used when performing the query */
    modifiers?: HogQLQueryModifiers
}
Enter fullscreen mode Exit fullscreen mode

那一刻你就会明白,你正在挖一个兔子洞,因为这些问题似乎是巨大的对象,它们在你的 API 中赋予不同的行为

经过几个小时,我开始意识到这些问题是如何影响查询的,例如:

  • 细分:更像是 GroupBy 子句
  • 属性:多态过滤,完全取决于查询
  • 系列:正在检索的数据类型(例如“浏览次数”)
  • 间隔:从/到日期的过滤。

这只是冰山一角。围绕PostHog的众多请求还有很多其他担忧。但问题如下:

  • 如果存在InsightsQueryBase,这些问题是否会与该类别的其他类型的查询共享?
  • 有没有办法让其他类型的基本查询也遇到同样的问题?
  • 我是否应该了解每种类型的**关注如何影响最终查询?

我的意思是,我们必须理解。也许不需要那么深入,但至少是常识。但如果文档没有提供示例,我们该如何获得证据呢?

好吧,这就是我最喜欢浏览器的地方:网络选项卡!

5. 网络选项卡

图片描述

我刚刚学习了如何通过对端点进行 AB 测试,直接从浏览器“探索” Web 应用程序。你可能会想:

“根据服务的不同,您无法构建自助用户机器人,因为它可能违反服务条款” - 普通用户

我会回答说我不在乎,因为我是为了学习目的才这么做的。如果我这么做,是为了学习事物的运作方式构建自己的工具使用他们的产品

只是一个随机事实:我从 2014 年开始检查元素/请求。我的第一个“真正的” GitHub项目基本上是检查来自一个名为“TribalWars”的浏览器游戏的所有请求,并构建一个CLI 应用程序/机器人通过我的终端玩游戏,最终目标是不玩它(建立一个自机器人帐户)。

图片描述

回到 PostHog 的实现,当我开始检查时,我发现每种查询都有不同类型的有效载荷。有了这些,我可以做出更多假设,并将其用于下一步。

{
    "client_query_id": "54f620e7-fdfe-4749-af41-caed0a3fe671",
    "query": {
        "breakdownFilter": {
            "breakdown": "$geoip_country_code",
            "breakdown_type": "event"
        },
        "conversionGoal": null,
        "dateRange": {
            "date_from": "-7d",
            "date_to": null
        },
        "filterTestAccounts": false,
        "kind": "TrendsQuery",
        "properties": [],
        "series": [
            {
                "event": "$pageview",
                "kind": "EventsNode",
                "math": "dau",
                "name": "Pageview"
            }
        ],
        "trendsFilter": {
            "display": "WorldMap"
        }
    },
    "refresh": "async"
}
Enter fullscreen mode Exit fullscreen mode

第二个查询只是为了以防万一:p

{
    "client_query_id": "25d4b82f-5bbe-4665-89a7-7aedcc7b103e",
    "query": {
        "dateRange": {
            "date_from": "-7d",
            "date_to": null
        },
        "filterTestAccounts": false,
        "kind": "RetentionQuery",
        "properties": [],
        "retentionFilter": {
            "period": "Week",
            "retentionReference": "total",
            "retentionType": "retention_first_time",
            "totalIntervals": 8
        }
    },
    "refresh": "async"
}
Enter fullscreen mode Exit fullscreen mode

我本来想包含结果查询,但那样代码量太大了,而且本节的重点已经讲完了。现在是时候完成整个流程了。

6.实施

图片描述

这时你就会开始思考:“现有的工具能满足我的需求吗?如果 SDK 没有提供完整类型的 API,我宁愿自己构建。我的意思是,我喜欢完全从头开始做事!是啊,就是那种重新发明轮子的感觉,你懂的。”

原因很简单:我是一名开发人员,通过重建事物并观察其行为来学习其工作原理。因此,我决定使用 PHP 构建一个 PostHog 专用查询生成器,以了解我这边的工作原理。

到目前为止,这就是我所得到的:

  • 我们有多种查询类型;
  • 这些查询可以继承属性/关注点,但不一定使用它们全部;
  • 查询共享过滤器/关注点;

那么我们该如何组织它呢?经过几个小时在“来源”“网络”选项卡中的挖掘,我得到了以下答案:

------
- QueryBuilder Feature at my own PostHogSDK
------

Query
├── Builders
│   ├── AbstractQueryBuilder.php
│   └── Insights
│       ├── AbstractInsightsQueryBuilder.php
│       ├── RetentionQueryBuilder.php
│       └── TrendsQueryBuilder.php
├── Filters
│   ├── Breakdown
│   │   ├── BreakdownFilter.php
│   │   ├── BreakdownTypeEnum.php
│   │   └── Concerns
│   │       └── InteractsWithBreakdown.php
│   ├── Compare
│   │   ├── CompareFilter.php
│   │   └── Concerns
│   │       └── InteractsWithCompare.php
│   ├── ConversionGoal
│   │   ├── ActionConversionGoal.php
│   │   ├── Concern
│   │   │   └── InteractsWithConversionGoal.php
│   │   ├── Contracts
│   │   │   └── ConversionGoalContract.php
│   │   └── CustomEventConversionGoal.php
│   ├── DateRange
│   │   ├── Concerns
│   │   │   └── InteractsWithDateRange.php
│   │   └── DateRangeFilter.php
│   ├── Interval
│   │   ├── Concerns
│   │   │   └── InteractsWithInterval.php
│   │   └── QueryIntervalEnum.php
│   ├── Node
│   │   ├── ActionNode.php
│   │   ├── Concerns
│   │   │   ├── InteractsWithMath.php
│   │   │   └── InteractsWithSeries.php
│   │   ├── Contracts
│   │   │   └── EntityNodeContract.php
│   │   ├── EntityNodeKindEnum.php
│   │   ├── EntityNode.php
│   │   ├── EventsNode.php
│   │   └── MathEnum.php
│   ├── Properties
│   │   ├── BaseProperty.php
│   │   ├── Concerns
│   │   │   └── InteractsWithProperties.php
│   │   ├── Contracts
│   │   │   └── PropertyFilterContract.php
│   │   ├── Filters
│   │   │   ├── EventPropertyFilter.php
│   │   │   └── SessionPropertyFilter.php
│   │   ├── PropertyFilterKind.php
│   │   └── PropertyOperator.php
│   └── Retention
│       ├── Concerns
│       │   └── InteractsWithRetention.php
│       ├── Enums
│       │   ├── RetentionPeriodEnum.php
│       │   ├── RetentionReferenceEnum.php
│       │   └── RetentionTypeEnum.php
│       └── RetentionFilter.php
├── QueryBuilderInterface.php
├── QueryFactory.php
└── QueryKindEnum.php
Enter fullscreen mode Exit fullscreen mode

每个过滤器都有自己的专用位置,可以使用PHP Traits将其继承到任何类型的构建器

trait InteractsWithDateRange  
{  
    protected ?DateRangeFilter $dateRange = null;  
    public function setDateRange(DateRangeFilter $dateRange): self  
    {  
        $this->dateRange = $dateRange;  

        return $this;  
    }    public function getDateRange(): ?DateRangeFilter  
    {  
        return $this->dateRange;  
    }  

    private function buildDateRange(array &$payload): void  
    {  
        if ($this->dateRange !== null) {  
            $payload['dateRange'] = [  
                'date_from' => $this->dateRange->from,  
                'date_to' => $this->dateRange->to,  
            ];        
        }    
    }
}
Enter fullscreen mode Exit fullscreen mode

因此,查询构建器的唯一要求是QueryBuilderContract提供“build()”方法,这样我就可以确保至少最终满足输入要求。下面类中的每个用法都代表了不同的 QueryBuilder 常见关注点。

class TrendsQueryBuilder extends AbstractInsightsQueryBuilder  
{  
    use InteractsWithInterval,  
        InteractsWithDateRange,  
        InteractsWithBreakdown,  
        InteractsWithCompare,  
        InteractsWithConversionGoal,  
        InteractsWithProperties,  
        InteractsWithSeries;  

    protected QueryKindEnum $queryType = QueryKindEnum::TrendsQuery;  

    public static function make(): self  
    {  
        return new self();  
    }  


    public function build(): array  
    {  
        return $this->jsonSerialize();  
    }  

    public function jsonSerialize(): array  
    {  
        $payload = parent::jsonSerialize();  

        $payload['kind'] = $this->queryType->value;  

        $this->buildDateRange($payload);  
        $this->buildInterval($payload);  
        $this->buildSeries($payload);  
        $this->buildCompare($payload);  
        $this->buildProperties($payload);  
        $this->buildConversionGoal($payload);  
        $this->buildBreakdown($payload);  

        return $payload;  
    }
}
Enter fullscreen mode Exit fullscreen mode

在编写代码时,我们必须加倍关注正在实现的每一个细节。坦白说,当我构建这种工具时,我主要会想到有人会使用它,这进一步提高了关注度。

这项研究的最终结果是为 PostHog API 提供功能齐全的趋势保留 查询生成器:


test('can build a retention query', function () {  
    $expected = [  
        "kind" => "RetentionQuery",  
        "dateRange" => [  
            "date_from" => "-7d",  
            "date_to" => null  
        ],  
        "filterTestAccounts" => false,  
        "retentionFilter" => [  
            "retentionType" => "retention_first_time",  
            "retentionReference" => "total",  
            "totalIntervals" => 8,  
            "period" => "Week"  
        ]  
    ];  
    $queryBuilder = RetentionQueryBuilder::make()  
        ->setDateRange(DateRangeFilter::from('-7d'))  
        ->setRetention(RetentionFilter::weekly()  
            ->setRetentionType(RetentionTypeEnum::FIRST_TIME)  
            ->setRetentionReference(RetentionReferenceEnum::Total)  
        );  
    expect($queryBuilder)->toBeInstanceOf(RetentionQueryBuilder::class)  
        ->and($queryBuilder->build())->toMatchArray($expected);  
});

test('can build a trends-query', function () {  
    $expected = [  
        'kind' => 'TrendsQuery',  
        'filterTestAccounts' => false,  
        'dateRange' => [  
            'date_from' => '-7d',  
            'date_to' => null,

        ],        
        'interval' => 'day',  
        'series' => [  
            [                
                'event' => '$pageview',  
                'kind' => 'EventsNode',  
                'math' => 'dau',  
                'name' => 'Pageview',  
            ],        
        ],        
            'compareFilter' => [  
            'compare' => true,  
        ],        
        'properties' => [  
            [                'type' => 'event',  
                'key' => '$host',  
                'operator' => 'exact',  
                'value' => 'api-main-ofjibb.laravel.cloud',  
            ],        
        ],    
    ];  
    $actual = TrendsQueryBuilder::make()  
        ->setDateRange(DateRangeFilter::from('-7d'))  
        ->setInterval(QueryIntervalEnum::Day)  
        ->addCompareFilter()  
        ->addSeries(EventsNode::make('$pageview', MathEnum::DAU, 'Pageview'))  
        ->addProperty(EventPropertyFilter::make('$host', PropertyOperator::Exact, 'api-main-ofjibb.laravel.cloud'));  


    expect($actual)->toBeInstanceOf(TrendsQueryBuilder::class)  
        ->and($actual->build())->toMatchArray($expected);  
});
Enter fullscreen mode Exit fullscreen mode

您可以点击此处查看此实现的 Pull 请求。欢迎您为该项目做出贡献!

7. 结论

这只是我对 PostHog API 的一项研究,我非常喜欢它,所以决定写一篇。从零开始构建并进行适当的研究,可以帮助你在另一个层面上学习

现在我可以告诉你,我可以非常顺利地使用 PostHog 平台,而不用担心本文第一部分提到的问题,因为我只是把它弄坏了一次,划分了关注点,并分别研究了每一个

这种方法——打破常规深入文档探索网络选项卡,然后从头开始重建——可以应用于任何技术。无论是PostHog、新的 API 还是框架,了解其内部原理都能让你成为更优秀的开发人员

就这样!希望你玩得开心,别忘了喝水!

文章来源:https://dev.to/danielhe4rt/how-i-learn-any-type-of-new-technology-as-a-senior-developer-47lj
PREV
如何自动保持 repo 包依赖项的更新
NEXT
Vue 最黑暗的一天 LongLiveSvelte