我如何学习任何类型的新技术(作为高级开发人员)
最近,我有一项任务,需要学习一种我从未使用过或见过相关内容的新工具,我想:为什么不写下我是如何学习它的呢?
这篇文章会教你从不同的角度学习事物。虽然方法可能不是最好的,但你可以复用这里提到的一些概念。
目录
1. 序言
最近我和朋友们用 PHP 和 Laravel 启动了一个新项目(又一个糟糕的 SaaS 应用程序)。项目本身并不重要,重要的是技术栈。为什么呢?因为我们没有足够的钱来购买自托管的基础设施,也没有钱购买好的付费工具……所以这很重要。
所以,如果我们要构建某种集成功能,至少需要有一个免费层级供用户使用,而不是实现起来像地狱一样。在思考这个问题时,我们偶然发现了一个问题:我们该如何进行用户跟踪/指标?
我的目标是展示SaaS 用户每页的分析数据。普通开发者可能会想:我们用Google Analytics和Google 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
}
这里我们可以假设几件事:
- 有一个继承的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
}
那一刻你就会明白,你正在挖一个兔子洞,因为这些问题似乎是巨大的对象,它们在你的 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"
}
第二个查询只是为了以防万一: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"
}
我本来想包含结果查询,但那样代码量太大了,而且本节的重点已经讲完了。现在是时候完成整个流程了。
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
每个过滤器都有自己的专用位置,可以使用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,
];
}
}
}
因此,查询构建器的唯一要求是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;
}
}
在编写代码时,我们必须加倍关注正在实现的每一个细节。坦白说,当我构建这种工具时,我主要会想到有人会使用它,这进一步提高了关注度。
这项研究的最终结果是为 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);
});
您可以点击此处查看此实现的 Pull 请求。欢迎您为该项目做出贡献!
7. 结论
这只是我对 PostHog API 的一项研究,我非常喜欢它,所以决定写一篇。从零开始构建并进行适当的研究,可以帮助你在另一个层面上学习。
现在我可以告诉你,我可以非常顺利地使用 PostHog 平台,而不用担心本文第一部分提到的问题,因为我只是把它弄坏了一次,划分了关注点,并分别研究了每一个。
这种方法——打破常规,深入文档,探索网络选项卡,然后从头开始重建——可以应用于任何技术。无论是PostHog、新的 API 还是框架,了解其内部原理都能让你成为更优秀的开发人员。
就这样!希望你玩得开心,别忘了喝水!
文章来源:https://dev.to/danielhe4rt/how-i-learn-any-type-of-new-technology-as-a-senior-developer-47lj