天天看點

1 小時頂 7 天!程式員工作中的巧思

> 原計劃 7 天的工作,1 小時完成!是我開挂了麼?

>

> 我最近在開發的項目,幫大家學程式設計:https://github.com/liyupi/code-nav

大家好,我是魚皮,今天分享自己工作中的小歡喜,也希望給大家帶來一些程式設計上的思考。

![](https://qiniuyun.code-nav.cn/xiuse.png)

### 孽起

事情是這樣的,最近在開發一個 **僅限内部使用** 的資料分析系統,我做後端,另外一個哥們做前端。

我們要實作的功能是:使用者可以在界面上任意輸入 SQL 資料查詢語句,并将它儲存下來,生成一個資料看闆。以後使用者可以随時打開這個看闆來浏覽和分析 SQL 查詢出的最新資料,而無需反複輸入 SQL 語句。

**舉個例子!**

假如我們有一個很大的資料倉庫,存了海量的資料,有男有女:

![](https://qiniuyun.code-nav.cn/image-20210519151359460.png)

産品同學可能隻想對部分資料進行分析,于是寫了下列 SQL 語句來查詢所有男性:

```sql

select * from table

    where 性别 = '男';

```

将該 SQL 語句儲存,得要一個 “男性資料看闆”,之後,就可以在該看闆頁面檢視和分析所有男性資料啦。

![資料看闆](https://qiniuyun.code-nav.cn/dsada.png)

要實作這個需求,一種最簡單的方式就是,直接将使用者在界面上輸入的 SQL 字元串發給後端儲存,需要看資料時,後端再用這個字元串從資料庫中查詢資料即可。

寫 SQL 配置流程:

![](https://qiniuyun.code-nav.cn/image-20210519153347369.png)

打開看闆浏覽資料流程:

![](https://qiniuyun.code-nav.cn/image-20210519153727995.png)

既然是允許使用者任意輸入的,那麼問題就來了。

假如小粗心不小心打錯了 SQL 語句:

# 錯誤 ❌

sleetc * from table

# 正确 ✅

select * from table;

又或者小迷糊記錯了 SQL 的文法:

select table from a;

select a from table;

甚至是小搗蛋不按規矩出牌,輸入一些亂七八糟的字元:

select q^q from table;

select q from table;

如果把這些錯誤的 SQL 語句發給後端,後端直接用它來查資料庫,必然會導緻查詢錯誤,查了個寂寞。

對于實時查詢來說,這沒啥問題,查詢失敗了大不了再修改語句查詢一次呗。

但我要做的需求是允許使用者将查詢語句作為看闆配置永久儲存下來,便于後續自動查詢資料。而且寫 SQL 配置的使用者可能和看資料的使用者不是同一個人,如果小 A 在配置時就沒有發現 SQL 語句是錯誤的,那到時候來檢視資料看闆的小 B 就會一臉懵逼,咋特麼看不到資料呢?是資料還沒準備好,還是查詢出來的資料就是 0 行呢,還是說我沒有浏覽權限呢?

他根本不會想到,已經配置成功的 SQL 語句,竟然是錯誤的!

![](https://qiniuyun.code-nav.cn/image-20210519154923225.png)

是以,需要在配置時就對使用者輸入的 SQL 進行校驗,看看它是否合法。

做個比喻,前端是一名底層員工(無知的小開發),後端是小組長,資料庫是大老闆。小開發做了個需求之後,應該先交給小組長檢查,小組長說沒問題之後,再給大老闆驗收。

**那如何校驗 SQL 語句呢?**

因為使用者的輸入是完全不确定的,他們寫的 SQL 語句可能又臭又長。是以我剛想到這個需求,就覺得腦闊疼,感覺賊麻煩,不保守地給自己計劃 7 天完成。

大家可以先想想如果讓你實作 SQL 語句校驗,你會怎麼做?

下面是我的思考過程。

![](https://qiniuyun.code-nav.cn/image-20210519141419098.png)

### 絞盡腦汁

首先,我們要明确:是在前端,還是在後端校驗?

其實,無論在前端還是後端,校驗都至關重要,可以有效防止很多錯誤的輸入。但由于最終是後端程式來直接操作資料庫,可以說是資料庫的最後一道防線,是以建議 **将校驗邏輯寫在後端**。資料庫很嫩,他自己把握不住,需要後端程式來幫他把握把握。

那如何在後端去校驗 SQL 呢?

#### 找現成的

首先,遇事不決問百度,不行再去搜倉庫。現在網上的開源項目很多,那不妨搜搜看,有沒有現成的 SQL 校驗類庫。最理想的情況是,有一個工具類函數,我傳給他 SQL 字元串作為參數,他直接傳回給我 true 或 false。

然而,我發現自己在想 peach,各種開源項目都搜遍了,沒有找到能開箱即用的 PostgreSQL 校驗庫。

![](https://qiniuyun.code-nav.cn/image-20210519160054841.png)

看來,隻能自己動手,豐衣足食了。

#### 模拟查詢

要自己實作校驗,我第一時間想到的方法是模拟一次查詢。使用者剛剛寫好 SQL 語句後,即便他現在并不需要浏覽資料查詢結果,我也可以在他儲存配置時,用他寫的 SQL 去查詢一次資料庫。假如查詢沒報錯,就說明 SQL 語句合法,允許儲存。

這種方式最直接,也最友善,基本沒有任何的開發成本,賊香!就好比一名小開發寫完爛代碼後,交給小組長,但小組長不講武德,自己看不懂代碼(也可能是不想看),索性就把代碼直接丢給大老闆,大老闆說沒問題了,小開發再上線。小組長狂喜!

![](https://qiniuyun.code-nav.cn/image-20210519161106302.png)

但是,有個緻命的問題:使用者在配置 SQL 語句時,資料表可能還沒準備好,無論語句是否正确,都無法查出資料。

是以,在将 SQL 語句直接發向資料庫前,要先确認資料表是否存在。若存在,可以通過模拟查詢的方式校驗;若不存在,隻能在後端通過其他方式校驗。

就好比小組長想把爛代碼直接丢給大老闆時,大老闆不在,這時,隻能靠自己來檢查了。

![](https://qiniuyun.code-nav.cn/3756e1c036f95b1b63e560cedb0a3ab0.jpg)

#### 正規表達式

要在程式中校驗字元串,我最先想到的是 **正規表達式**,即用特定文法來比對同一類具有相似規則的字元串,常見的有校驗手機号、校驗郵箱、校驗身份證等。

在使用正規表達式進行校驗前,我們要先對字元串進行分析,看它們是否具有相似的結構、哪些部分相似。比如 QQ 郵箱,結構很規整,基本都是 `[email protected]`,是以,可以用正規表達式 `/^\[email protected]$/` 來校驗。

回過頭來看我們的需求,要校驗的是 SQL 語句,似乎也比較規整,無非就是查詢哪個表、選哪些行、選哪些列、怎麼排序等等,大概的結構是這樣:

SELECT select_list

[ INTO new_table ]

FROM table_source

[ WHERE search_condition ]

[ GROUP BY group_by_expression ]

[ HAVING search_condition ]

[ ORDER BY order_expression [ ASC | DESC ] ]

根據這個結構,很容易編寫出粗略的正規表達式。但是,資料業務中的 SQL 語句可比這複雜得多,包含各種四則運算、IF ... ELSE 條件判斷、CASE ... WHEN ... 分支,字元串、日期類型處理函數,還有各種聚合函數等,比如下面這個 SQL:

select a as b,

    sum(case when (false) then d / a else 2 end) as c

    from table

    where a = 1

    group by b, c;

如果以上這些零碎的文法都用正規表達式來比對,可就太麻煩了!想想腦闊又疼了。

![](https://qiniuyun.code-nav.cn/image-20210519150025431.png)

#### 解析表達式

既然編寫一套正規表達式比較麻煩,那我能想到的就隻有把 SQL 打的稀吧碎了。可以用類似編譯原理文法分析的方式,搞一個 SQL 解析器,将完整的 SQL 語句轉換為一顆抽象文法樹(AST),每個節點都是一個小表達式,進而能夠更精細地校驗 SQL 語句的合法性。

![SQL 表達式抽象文法樹](https://qiniuyun.code-nav.cn/esaeas.png)

如果自己從零開始實作這樣一套 SQL 解析器,實在是太麻煩了,而且不具備一定的專業知識也寫不出來。是以,我先到網上去搜尋一番,看看有沒有現成的解析器引擎。

這次的搜尋結果還算滿意,找到了一些知名解析引擎,但是看了一圈,讀了半天,發現很難直接去使用他們的源碼。那委曲求全的方式就是照着他們的源碼自己寫一個解析器了。

想到這裡,頭頂不僅感受到了一絲寒涼,感覺給自己估時 7 天都少了。

![](https://qiniuyun.code-nav.cn/image-20210519145910318.png)

### 移花接木

第二天,我又思考了一下,網上有那麼多現成的類庫,難道就沒有一個能滿足我的需求?即使沒有完全現成的,能不能找個相對好用的呢?

畢竟自己來寫這複雜的校驗邏輯實在太麻煩了,是以我必須再掙紮一下!

于是,我掏出了禦用小黃鴨,開始對着它念叨:SQL 校驗、SQL 校驗、SQL 校驗。。。

![](https://qiniuyun.code-nav.cn/xiaohuangya.png)

我:什麼時候會用到 SQL 校驗呢?

小黃鴨:需要查資料庫的時候。

我:什麼東西會去查資料庫呢?

小黃鴨:架構、資料庫連接配接池、或者代理。

我:那這些玩意在查資料庫的時候,會幫我們做校驗麼?

小黃鴨:校驗校驗,你就知道校驗,你需要的功能一定是校驗麼?

**等等,我好像恍然大悟了!**

既然沒辦法直接搜到現成的 SQL 校驗類庫,那不妨來個 **移花接木**,想一想其他的類庫中是否包含 SQL 解析功能,如果解析失敗,不就表示 SQL 非法,校驗不通過麼!

![](https://qiniuyun.code-nav.cn/image-20210519145322767.png)

我開始回想自己以前用過的和通路資料庫有關的技術,突然想到,阿裡的 Druid 資料庫連接配接池類庫好像有一個 SQL 語句格式化的功能,能把雜亂的 SQL 重新排版。既然能對 SQL 格式化,是不是意味着,這個類庫有能力對 SQL 語句進行解析呢?

仔細一查 Druid 的文檔,發現還真有一個類叫 `SQLUtils`,這個類有一個方法叫 `parseStatements`,可以對多種不同的 SQL 方言進行解析,比如 MySQL、PostgreSQL 等。

```java

// 解析,接受 sql 語句和資料庫方言為參數

SQLUtils.parseStatements(sql, POSTGRESQL);

解析失敗時,會抛出異常,表示 SQL 語句非法,正好能夠滿足我的需求!

最終,我寫出的代碼如下:

try {

  String sql = "select * from a";

  SQLUtils.parseStatements(sql, POSTGRESQL);

  return true;

} catch (ParserException e) {

  LOGGER.error("解析失敗", e);

    return false;

}

幾分鐘就寫完了代碼,然後又花了一些時間輸入各種 SQL 語句來測試,雖然隻能實作基本的文法校驗,但綜合衡量效果和成本上,我覺得已經不錯了,省下的大量時間可以繼續完善和優化項目的其他代碼。

關鍵是,心不累了,頭發又支棱起來了!

![](https://qiniuyun.code-nav.cn/image-20210519142238806.png)

---

通過這件事,帶給我三點思考:

1. 在我們找項目代碼、找類庫的時候,如果沒辦法找到直接滿足需求的,那麼可以把思維從整體轉向局部,想想在其他的項目中是否包含了你要找的功能。就像查詞典一樣,你要查單詞 `apple`,但是翻目錄隻有首字母 `a`,這個時候,就不能隻盯着 `a` 看,而是要看到詞典裡面的内容,其實 `apple` 就藏在 `a` 之中。

2. 前人栽樹,後人乘涼,現在網上現成的項目代碼太多了,如果不是為了學習,很多東西沒必要自己再去實作一遍。

3. 寫代碼時要注重積累,多學習和了解技術,并歸納總結到你的武器庫中,否則前人栽的樹你找不到,就可惜了。

![](https://qiniuyun.code-nav.cn/image-20210519141937307.png)

當然,有條件的話,前端也是可以加校驗的,但目前沒啥必要,這裡我們先用 `CodeMirror` 做一個 SQL 代碼高亮來替代。

![](https://qiniuyun.code-nav.cn/image-20210519141819670.png)

如果真的讓你實作前端 SQL 校驗,你會怎麼做呢?

我是魚皮,原創不易,如果覺得文章還不錯的話,希望朋友們 **點贊** 支援下,給俺點創作動力。

最近還在開發我的 **程式設計導航**(https://www.code-nav.cn),一個幫大家找程式設計資源的項目,歡迎使用!

![各種程式設計資源](https://qiniuyun.code-nav.cn/%E6%9C%8B%E5%8F%8B%E5%9C%88%E9%85%8D%E5%9B%BE-20210524231057095.png)

我是如何在大學期間通過自學,拿到騰訊、位元組等大廠 offer 的,可以看這篇文章,不再迷茫!

[我學計算機的四年,共勉!](https://t.1yb.co/q0mS)