最近,我有一個任務是學習一個我從未使用過或見過相關內容的新工具,我想:為什麼不寫下我如何學習它的過程呢?
這就是一篇從不同視角教你學習事物的文章。雖然不一定是最好的方法,但你可以重用這裡寫的一些概念。
最近我和朋友開始了一個新專案(又一個糟糕的虛擬軟體即服務應用)使用 PHP 和 Laravel。專案本身並不重要,但 技術棧 是關鍵。為什麼呢?因為我們 沒有足夠的財力去花錢在自架的基礎設施上,也沒有預算購買良好的付費工具……所以這點很重要。
因此,如果我們要構建一些具有任何整合功能的東西,它至少需要有一個免費層可以使用,且不能是 實作起來讓人抓狂 的工具。在思考這一點時,我們困惑了:我們要怎麼進行使用者的 追蹤/指標 呢?
我的目標是 展示每頁 SaaS 使用者的分析。普通開發者可能會想:我們可以使用 Google Analytics 和 Google Tag Manager,對吧?好吧……讓我告訴你查斯特曾經說過的一句話:
我努力了這麼久,卻沒什麼用,最後一切似乎都不重要 - 查斯特·貝寧頓
老兄,我一直是個 Google UI 反感者。但這次不是為了工作,而是為了我自己,而我 沒有多餘的時間。所以,我花了 4 小時嘗試……在我的專案上設置基本配置並理解如何創建一個簡單的標籤?最後我 浪費了時間,因為什麼都沒有運作。
像是,為什麼所有的 Google 產品都有一個你需要花費幾乎一個博士學位才能使用的介面?到處都是太多的資訊!
就在這時,我的朋友告訴我關於 PostHog,一個開源的分析平台,它讓你可以使用自架或雲端選項追蹤用戶行為(而在實作上超越 GTM/GA4)。但同時,我希望你能耐心看到這篇文章的結尾,因為這項研究花了我一些小時和幾行程式碼。
好吧,這是最明顯的事情,但必須說明:當你嘗試一個新工具時,你必須玩弄它的各個方面。我知道我們是開發者,但 產品的 UI 是在試圖告訴你一些東西。
這個 PostHog 的過程花了 無限的時間來設置,這無疑是一個優點。然而,UI 上發生了太多事情,我的第一個想法是嘗試 點擊所有東西 並 在紀錄時間內摧毀我正在做的專案。
想想看:如果你只因為在雲端或「生產」環境中而害怕使用一个平台/產品,那麼最後你什麼也學不會。
在這一步結束時,我只刪除了這個毀損的專案,並重新開始一個新的。但在這一點上,我已經知道了 UI 的運作方式。
警告: 別在你公司雲端帳號中進行這樣的操作,建一個獨立的帳號並在自己的環境中嘗試!!
好吧……我必須坦誠告訴你,有時候摧毀一切並不是答案,因為這甚至不是個問題 這取決於你正在使用的功能。在 PostHog,他們擁有 「TrendsQuery」、「RetentionQuery」、「Web Vitals」 和對於每種類型查詢非常 華麗的 UI 查詢生成器。非常令人印象深刻!但是……我無法立即使用它。
由於我有一種叫做 ADHD 的驚人狀況,當我看到最小的 包含條件元件的複雜 UI 出現在螢幕上時,我會 完全崩潰並開始恐慌。但這不應該是放棄的理由,對吧?
這部分並不是對產品的批評,而是我想傾訴的心聲。數據科學是一個無限條件規則的領域,擁有越多的資訊,其準確性就會越高,而我正在邁出學習的第一步。
當然,文件!我為什麼之前不研究這個呢?我是說,這是開發者的天地,對吧?對!但我們是 將使用產品的開發者,所以第一步是必須的。
我通常不會看教程,儘管我知道每個人都應該。我只是不喜歡,對我來說 好的 API 參考必須告訴你所有事情,但在我看來所有事情會是什麼呢?
在大多數情況下,你會看到許多像這樣的元素,但這取決於你所使用的 API。以我這次使用 PostHog 特定實作 為例,挑戰在於 /query/create 端點具有 多型請求和響應,因此並沒有響應有效載荷。
所以,這時我們開始深入研究實際的代碼,因為我們需要理解這些端點如何與實際的實作一起運作。
我本可以只是寫下 HogQL 查詢,但對我來說,學這個會花費更多時間。
到這個時候我們知道我們想要什麼,對吧?我們想了解每一種類型請求所傳送的有效載荷。在我這個案例中,我想理解 Retention 和 Trends Query 在 PostHog 上如何運作。由於該產品是開源的,我可以直接從 GitHub 上閱讀它!
前端是用 TypeScript 寫的,後端則是用 Python 寫的。因為我是後端開發者,你可能會認為我會選擇 Python 實作來深入探索,對吧?其實,我因為很多原因一直討厭 Python。此外,TypeScript 提供了我可以用作代碼搜尋參考的類型安全。
在 TypeScript 原始碼 中,我找到了客戶端所需的一切實作,但看到之後,我直奔進入了兔子洞,你會明白為什麼。
看這段程式碼(參考):
export interface TrendsQuery extends InsightsQueryBase<TrendsQueryResponse> {
kind: NodeKind.TrendsQuery
/**
* 回應的細分程度。可以是 `hour`、`day`、`week` 或 `month`
*
* @default day
*/
interval?: IntervalType
/** 要包含的事件和行動 */
series: AnyEntityNode[]
/** 特定於趨勢分析的屬性 */
trendsFilter?: TrendsFilter
/** 事件和行動的細分 */
breakdownFilter?: BreakdownFilter
}
在這裡我們可以假設幾個事情:
首先,我們需要知道基礎是什麼,並尋找 InsightsQuery 關聯:
/** Insight 查詢節點的基類。不得直接使用。 */
export interface InsightsQueryBase<R extends AnalyticsQueryResponseBase<any>> extends Node<R> {
/** 查詢日期範圍 */
dateRange?: DateRange
/**
* 通過應用相應的過濾器來排除內部和測試用戶
*
* @default false
*/
filterTestAccounts?: boolean
/**
* 針對所有系列的屬性過濾器
*
* @default []
*/
properties?: AnyPropertyFilter[] | PropertyGroupFilter
/**
* 群組彙總
*/
aggregation_group_type_index?: integer | null
/** 抽樣率 */
samplingFactor?: number | null
/** Insight 的可視化中使用的顏色 */
dataColorTheme?: number | null
/** 執行查詢時使用的修飾符 */
modifiers?: HogQLQueryModifiers
}
然後你會明白你正在 挖掘一個兔子洞,因為這些 關聯 看起來是巨大 物件,賦予 API 不同的行為。
經過幾小時的學習,我開始認識到這些關聯如何影響查詢,像是:
這只是 冰山一角。圍繞 PostHog 的許多請求還有很多其他關聯。但這裡問題是:
我是說,我們必須理解。也許不需要那麼深入,但至少要有個大概了解。但如果文件未提供例子,我們如何取得證據?
好吧,那就是我解釋我最喜歡的瀏覽器功能:網路標籤!
我剛學會了如何 「探索」 從瀏覽器直接訪問網路應用的過程,通過對端點進行 AB 測試。你可能會想:
「根據服務的不同,你無法建立一個自用機器人,因為這可能違反服務條款」 - 普通用戶
我會回答說 我一點也不在乎,因為我這樣做是為了 學習目的。如果我這樣做,那是為了 理解事物的運作方式、建立自己的工具 和 使用他們的產品。
隨便說一下:我自 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"
}
我本來會包含結果查詢,但那會太多代碼,這部分的重點已經說明清楚了。現在是時候結束整個流程了。
這時候你開始思考,「可用的工具是否符合我的需求?如果 SDK 沒有提供完全類型的 API,我寧願自己建立東西。我是說,我喜歡從 零開始 做事情!是的,就是那種重新發明輪子的尿性,你知道的?
原因很簡單:我是個通過重建事物並觀察 它們的行為 來學習的人。而且因此,我決定使用 PHP 建立一個 PostHog 專用查詢生成器,以了解事物如何在我這邊運作。
到目前為止,我得到的結果是:
那麼,我們該如何組織呢?經過數小時在 原始碼 和 網路 標籤的挖掘後,這是我的答案:
------
- 查詢生成器功能在我自己的 PostHogSDK 中
------
查詢
├── 生成器
│ ├── AbstractQueryBuilder.php
│ └── Insights
│ ├── AbstractInsightsQueryBuilder.php
│ ├── RetentionQueryBuilder.php
│ └── TrendsQueryBuilder.php
├── 過濾器
│ ├── 細分
│ │ ├── BreakdownFilter.php
│ │ ├── BreakdownTypeEnum.php
│ │ └── 關聯
│ │ └── InteractsWithBreakdown.php
│ ├── 比較
│ │ ├── CompareFilter.php
│ │ └── 關聯
│ │ └── InteractsWithCompare.php
│ ├── 轉換目標
│ │ ├── ActionConversionGoal.php
│ │ ├── 關聯
│ │ │ └── InteractsWithConversionGoal.php
│ │ ├── 合約
│ │ │ └── ConversionGoalContract.php
│ │ └── CustomEventConversionGoal.php
│ ├── 日期範圍
│ │ ├── 關聯
│ │ │ └── InteractsWithDateRange.php
│ │ └── DateRangeFilter.php
│ ├── 間隔
│ │ ├── 關聯
│ │ │ └── InteractsWithInterval.php
│ │ └── QueryIntervalEnum.php
│ ├── 節點
│ │ ├── ActionNode.php
│ │ ├── 關聯
│ │ │ ├── InteractsWithMath.php
│ │ │ └── InteractsWithSeries.php
│ │ ├── 合約
│ │ │ └── EntityNodeContract.php
│ │ ├── EntityNodeKindEnum.php
│ │ ├── EntityNode.php
│ │ ├── EventsNode.php
│ │ └── MathEnum.php
│ ├── 屬性
│ │ ├── BaseProperty.php
│ │ ├── 關聯
│ │ │ └── InteractsWithProperties.php
│ │ ├── 合約
│ │ │ └── PropertyFilterContract.php
│ │ ├── 過濾
│ │ │ ├── EventPropertyFilter.php
│ │ │ └── SessionPropertyFilter.php
│ │ ├── PropertyFilterKind.php
│ │ └── PropertyOperator.php
│ └── 保留
│ ├── 關聯
│ │ └── InteractsWithRetention.php
│ ├── 列舉
│ │ ├── 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,
];
}
}
}
所以查詢生成器的唯一要求是必須擁有我提供的 "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 的完整 Trends 和 Retention QueryBuilder:
test('能夠構建保留查詢', 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('能夠構建趨勢查詢', 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 Request。也歡迎你為這個專案做出貢獻!
這只是一個我 非常享受並決定寫下來的 PostHog API 的 學習研究。從 零開始構建事物和做適當的研究 是幫助你在 另一個層面上學習事物 的好方法。
現在我可以告訴你,我可以以 非常流暢的方式 使用 PostHog 平台,不再擔心這篇文章第一部分提到的問題,因為 我只摧毀過一次,分開了關聯,並分別研究了每一個。
這種方法——摧毀事物、深入文檔、探索網路標籤和 從頭重建——可以應用於 任何技術。無論是 PostHog、新的 API,還是某個框架,理解內部邏輯使你成為一個更好的開發者。
就是這樣!希望你喜歡,別忘了喝水!
原文出處:https://dev.to/danielhe4rt/how-i-learn-any-type-of-new-technology-as-a-senior-developer-47lj