JavaScript代碼風(fēng)格要素

原文地址

譯者:墨白 校對:野草

本文已在前端早讀課公眾號首發(fā):【第952期】JavaScript代碼風(fēng)格要素

Out of the Blue?—?In?aki Bolumburu (CC BY-NC-ND 2.0)
Out of the Blue?—?In?aki Bolumburu (CC BY-NC-ND 2.0)

1920年,由威廉·斯特倫克(William Strunk jr .)撰寫的《英語寫作手冊:風(fēng)格的要素(The Elements of Style)》出版了,這本書列舉了7條英文寫作的準(zhǔn)則,過了一個世紀(jì),這些準(zhǔn)則并沒有過時。對于工程師來說,你可以在自己的編碼風(fēng)格中應(yīng)用類似的建議來指導(dǎo)日常的編碼,提高自己的編碼水平。

需要注意的是,這些準(zhǔn)則不是一成不變的法則。如果違背它們,能夠讓代碼可讀性更高,那么便沒有問題,但請?zhí)貏e小心并時刻反思。這些準(zhǔn)繩是經(jīng)受住了時間考驗的,有充分的理由說明:它們通常是正確的。如果要違背這些規(guī)則,一定要有充足的理由,而不要單憑一時的興趣或者個人的風(fēng)格偏好。

書中的寫作準(zhǔn)則如下:

  • 以段落為基本單位:一段文字,一個主題。
  • 刪減無用的語句。
  • 使用主動語態(tài)。
  • 避免一連串松散的句子。
  • 相關(guān)的內(nèi)容寫在一起。
  • 從正面利用肯定語句去發(fā)表陳述。
  • 不同的概念采用不同的結(jié)構(gòu)去闡述。

我們可以應(yīng)用相似的理念到代碼編寫上面:

  1. 一個function只做一件事,讓function成為代碼組合的最小單元。
  2. 刪除不必要的代碼。
  3. 使用主動語態(tài)。
  4. 避免一連串結(jié)構(gòu)松散的,不知所云的代碼。
  5. 將相關(guān)的代碼寫在一起。
  6. 利用判斷true值的方式來編寫代碼。
  7. 不同的技術(shù)方案利用不同的代碼組織結(jié)構(gòu)來實現(xiàn)。

1.一個function只做一件事,讓function成為代碼組合的最小單元

軟件開發(fā)的本質(zhì)是“組合”。 我們通過組合模塊,函數(shù)和數(shù)據(jù)結(jié)構(gòu)來構(gòu)建軟件。理解如果編寫以及組合方法是軟件開發(fā)人員的基本技能。

模塊是一個或多個function和數(shù)據(jù)結(jié)構(gòu)的簡單集合,我們用數(shù)據(jù)結(jié)構(gòu)來表示程序狀態(tài),只有在函數(shù)執(zhí)行之后,程序狀態(tài)才會發(fā)生一些有趣的變化。

JavaScript中,可以將函數(shù)分為3種:

  • I/O 型函數(shù) (Communicating Functions):函數(shù)用來執(zhí)行I/O。
  • 過程型函數(shù) (Procedural Functions):對一系列的指令序列進(jìn)行分組。
  • 映射型函數(shù) (Mapping Functions):給定一些輸入,返回對應(yīng)的輸出。

有效的應(yīng)用程序都需要I/O,并且很多程序都遵循一定的程序執(zhí)行順序,這種情況下,程序中的大部分函數(shù)都會是映射型函數(shù):給定一些輸入,返回相應(yīng)的輸出。

每個函數(shù)只做一件事情:如果你的函數(shù)主要用于I/O,就不要在其中混入映射型代碼,反之亦然。嚴(yán)格根據(jù)定義來說,過程型函數(shù)違反了這一指導(dǎo)準(zhǔn)則,同時也違反了另一個指導(dǎo)準(zhǔn)則:避免一連串結(jié)構(gòu)松散,不知所云的代碼。

理想中的函數(shù)是一個簡單的、明確的純函數(shù):

  • 同樣的輸入,總是返回同樣的輸出。
  • 無副作用。

也可以查看,“什么是純函數(shù)?”

2. 刪除不必要的代碼

簡潔的代碼對于軟件而言至關(guān)重要。更多的代碼意味更多的bug隱藏空間。更少的代碼 = 更少的bug隱藏空間 = 更少的bug

簡潔的代碼讀起來更清晰,因為它擁有更高的“信噪比”:閱讀代碼時更容易從較少的語法噪音中篩選出真正有意義的部分。可以說,更少的代碼 = 更少的語法噪聲 = 更強(qiáng)的代碼含義信息傳達(dá)

借用《風(fēng)格的元素》這本書里面的一句話就是:簡潔的代碼更健壯。

function secret (message) {
  return function () {
    return message;
  }
};

可以簡化成:

const secret = msg => () => msg;

對于那些熟悉簡潔箭頭函數(shù)寫法的開發(fā)來說,可讀性更好。它省略了不必要的語法:大括號,function關(guān)鍵字以及return語句。

而簡化前的代碼包含的語法要素對于傳達(dá)代碼意義本身作用并不大。它存在的唯一意義只是讓那些不熟悉ES6語法的開發(fā)者更好的理解代碼。

ES6自2015年已經(jīng)成為語言標(biāo)準(zhǔn),是時候去學(xué)習(xí)它了。

刪除不必要的代碼

有時候,我們試圖為不必要的事物命名。問題是人類的大腦在工作中可用的記憶資源有限,每個名稱都必須作為一個單獨(dú)的變量存儲,占據(jù)工作記憶的存儲空間。

由于這個原因,有經(jīng)驗的開發(fā)者會盡可能地刪除不必要的變量。

例如,大多數(shù)情況下,你應(yīng)該省略僅僅用來當(dāng)做返回值的變量。你的函數(shù)名應(yīng)該已經(jīng)說明了關(guān)于函數(shù)返回值的信息。看看下面的:

const getFullName = ({firstName, lastName}) => {
  const fullName = firstName + ' ' + lastName;
  return fullName;
};

對比

const getFullName = ({firstName, lastName}) => (
  firstName + ' ' + lastName
);

另一個開發(fā)者通常用來減少變量名的做法是,利用函數(shù)組合以及point-free-style

Point-free-style是一種定義函數(shù)方式,定義成一種與參數(shù)無關(guān)的合成運(yùn)算。實現(xiàn)point-free風(fēng)格常用的方式包括函數(shù)科里化以及函數(shù)組合。

讓我們來看一個函數(shù)科里化的例子:

const add2 = a => b => a + b;
// Now we can define a point-free inc()
// that adds 1 to any number.
const inc = add2(1);
inc(3); // 4

看一下inc()函數(shù)的定義方式。注意,它并未使用function關(guān)鍵字,或者=>語句。add2也沒有列出一系列的參數(shù),因為該函數(shù)不在其內(nèi)部處理一系列的參數(shù),相反,它返回了一個知道如何處理參數(shù)的新函數(shù)。

函數(shù)組合是將一個函數(shù)的輸出作為另一函數(shù)的輸入的過程。 也許你沒有意識到,你一直在使用函數(shù)組合。鏈?zhǔn)秸{(diào)用的代碼基本都是這個模式,比如數(shù)組操作時使用的.map(),Promise 操作時的promise.then()。函數(shù)組合在函數(shù)式語言中也被稱之為高階函數(shù),其基本形式為:f(g(x))。

當(dāng)兩個函數(shù)組合時,無須創(chuàng)建一個變量來保存兩個函數(shù)運(yùn)行時的中間值。我們來看看函數(shù)組合是怎么減少代碼的:

const g = n => n + 1;
const f = n => n * 2;
// 需要操作參數(shù)、并且存儲中間結(jié)果
const incThenDoublePoints = n => {
  const incremented = g(n);
  return f(incremented);
};
incThenDoublePoints(20); // 42

// compose2 - 接受兩個函數(shù)作為參數(shù),直接返回組合
const compose2 = (f, g) => x => f(g(x));
const incThenDoublePointFree = compose2(f, g);
incThenDoublePointFree(20); // 42

你可以利用函子(functor)來做同樣的事情。在函子中把參數(shù)封裝成可遍歷的數(shù)組。讓我們利用函子來寫另一個版本的compose2

const compose2 = (f, g) => x => [x].map(g).map(f).pop();
const incThenDoublePointFree = compose2(f, g);
incThenDoublePointFree(20); // 42

當(dāng)每次使用promise鏈時,你就是在做這樣的事情。

幾乎每一個函數(shù)式編程類庫都提供至少兩種函數(shù)組合方法:從右到左依次運(yùn)行的compose();從左到右依次運(yùn)行的pipe()

Lodash中的compose()以及flow()分別對應(yīng)這兩個方法。下面是使用pipe的例子:

import pipe from 'lodash/fp/flow';
pipe(g, f)(20); // 42

下面的代碼也做著同樣的事情,但代碼量并未增加太多:

const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
pipe(g, f)(20); // 42

如果函數(shù)組合這個名詞聽起來很陌生,你不知道如何使用它,請仔細(xì)想一想:

軟件開發(fā)的本質(zhì)是組合,我們通過組合較小的模塊,方法以及數(shù)據(jù)結(jié)構(gòu)來構(gòu)建應(yīng)用程序。

不難推論,工程師理解函數(shù)和對象組合這一編程技巧就如同搞裝修需要理解鉆孔機(jī)以及氣槍一樣重要。

當(dāng)你利用“命令式”代碼將功能以及中間變量拼湊在一起時,就像瘋狂使用膠帶和膠水將這些部分胡亂粘貼起來一樣,而函數(shù)組合看上去更流暢。

記住:

  • 用更少的代碼。
  • 用更少的變量。

3. 使用主動語態(tài)

主動語態(tài)比被動語態(tài)更直接,跟有力量,盡量多直接命名事物:

  • myFunction.wasCalled()優(yōu)于myFunction.hasBeenCalled()
  • createUser優(yōu)于User.create()
  • notify()優(yōu)于Notifier.doNotification()

命名布爾返回值時最好直接反應(yīng)其輸出的類型:

  • isActive(user)優(yōu)于getActiveStatus(user)
  • isFirstRun = false;優(yōu)于firstRun = false;

函數(shù)名采用動詞形式:

  • increment()優(yōu)于plusOne()
  • unzip()優(yōu)于filesFromZip()
  • filter(fn, array)優(yōu)于matchingItemsFromArray(fn, array)

事件處理

事件處理以及生命周期函數(shù)由于是限定符,比較特殊,就不適用動詞形式這一規(guī)則;相比于“做什么”,它們主要用來表達(dá)“什么時候做”。對于它們,可以“<什么時候去做>,<動作>”這樣命名,朗朗上口。

  • element.onClick(handleClick)優(yōu)于element.click(handleClick)
  • element.onDragStart(handleDragStart)優(yōu)于component.startDrag(handleDragStart)

上面兩例的后半部分,它們讀起來更像是正在嘗試去觸發(fā)一個事件,而不是對其作出回應(yīng)。

生命周期函數(shù)

對于組件生命周期函數(shù)(組件更新之前調(diào)用的方法),考慮一下以下的命名:

  • componentWillBeUpdated(doSomething)
  • componentWillUpdate(doSomething)
  • beforeUpdate(doSomething)

第一個種我們使用了被動語態(tài)(將要被更新而不是將要更新)。這種方式很口語化,但含義表達(dá)并沒有比其它兩種方式更清晰。

第二種就好多了,但生命周期函數(shù)的重點(diǎn)在于觸發(fā)處理事件。componentWillUpdate(handler)讀起來就好像它將立即觸發(fā)一個處理事件,但這不是我們想要表達(dá)的。我們想說,“在組件更新之前,觸發(fā)事件”。beforeComponentUpdate()能更清楚的表達(dá)這一想法。

進(jìn)一步簡化,因為這些方法都是組件內(nèi)置的。在方法名中加入component是多余的。想一想如果你直接調(diào)用這些方法時:component.componentWillUpdate()。這就好像在說,“吉米吉米在晚餐吃牛排。”你沒有必要聽到同一個對象的名字兩次。顯然,

  • component.beforeUpdate(doSomething)優(yōu)于component.beforeComponentUpdate(doSomething)

函數(shù)混合是指將方法作為屬性添加到一個對象上面,它們就像裝配流水線給傳進(jìn)來的對象加上某些方法或者屬性。

我喜歡用形容詞來命名函數(shù)混合。你也可以經(jīng)常使用"ing"或者"able"后綴來找到有意義的形容詞。例如:

  • const duck = composeMixins(flying, quacking);
  • const box = composeMixins(iterable, mappable);

4.避免一連串結(jié)構(gòu)松散的,不知所云的代碼

開發(fā)人員經(jīng)常將一系列事件串聯(lián)在一個進(jìn)程中:一組松散的、相關(guān)度不高的代碼被設(shè)計依次運(yùn)行。從而很容易形成“意大利面條”代碼。

這種寫法經(jīng)常被重復(fù)調(diào)用,即使不是嚴(yán)格意義上的重復(fù),也只有細(xì)微的差別。例如,界面不同組件之間幾乎共享相同的核心需求。 其關(guān)注點(diǎn)可以分解成不同生命周期階段,并由單獨(dú)的函數(shù)方法進(jìn)行管理。

考慮以下的代碼:

const drawUserProfile = ({ userId }) => {
  const userData = loadUserData(userId);
  const dataToDisplay = calculateDisplayData(userData);
  renderProfileData(dataToDisplay);
};

這個方法做了三件事:獲取數(shù)據(jù),根據(jù)獲取的數(shù)據(jù)計算view的狀態(tài),以及渲染。

在大部分現(xiàn)代前端應(yīng)用中,這些關(guān)注點(diǎn)中的每一個都應(yīng)該考慮分拆開。通過分拆這些關(guān)注點(diǎn),我們可以輕松地為每個問題提供不同的函數(shù)。

比如,我們可以完全替換渲染器,它不會影響程序的其他部分。例如,React的豐富的自定義渲染器:適用于原生iOS和Android應(yīng)用程序的ReactNative,WebVR的AFrame,用于服務(wù)器端渲染的ReactDOM/Server 等等...

drawUserProfile的另一個問題就是你不能在沒有數(shù)據(jù)的情況下,簡單地計算要展示的數(shù)據(jù)并生成標(biāo)簽。如果數(shù)據(jù)已經(jīng)在其他地方加載過了會怎么樣,就會做很多重復(fù)和浪費(fèi)的事情。

分拆關(guān)注點(diǎn)也使得它們更容易進(jìn)行測試。我喜歡對我的應(yīng)用程序進(jìn)行單元測試,并在每次修改代碼時查看測試結(jié)果。但是,如果我們將渲染代碼和數(shù)據(jù)加載代碼寫在一起,我不能簡單地將一些假數(shù)據(jù)傳遞給渲染代碼進(jìn)行測試。我必須從端到端測試整個組件。而這個過程中,由于瀏覽器加載,異步I/O請求等等會耗費(fèi)時間。

上面的drawUserProfile代碼不能從單元測試測試中得到即時反饋。而分拆功能點(diǎn)允許你進(jìn)行單獨(dú)的單元測試,得到測試結(jié)果。

上文已經(jīng)已經(jīng)分析出單獨(dú)的功能點(diǎn),我們可以在應(yīng)用程序中提供不同的生命周期鉤子給其調(diào)用。 當(dāng)應(yīng)用程序開始裝載組件時,可以觸發(fā)數(shù)據(jù)加載。可以根據(jù)響應(yīng)視圖狀態(tài)更新來觸發(fā)計算和渲染。

這么做的結(jié)果是軟件的職責(zé)進(jìn)一步明確:每個組件可以復(fù)用相同的結(jié)構(gòu)和生命周期鉤子,并且軟件性能更好。在后續(xù)開發(fā)中,我們不需要重復(fù)相同的事。

5.功能相連的代碼寫在一起

許多框架以及boilerplates規(guī)定了程序文件組織的方法,其中文件按照代碼類別分組。如果你正在構(gòu)建一個小的計算器,獲取一個待辦事宜的app,這樣做是很好的。但是對于較大的項目,通過業(yè)務(wù)功能特性將文件分組在一起是更好的方法。

按代碼類別分組:

.
├── components
│   ├── todos
│   └── user
├── reducers
│   ├── todos
│   └── user
└── tests
    ├── todos
    └── user

按業(yè)務(wù)功能特性分組:

.
├── todos
│   ├── component
│   ├── reducer
│   └── test
└── user
    ├── component
    ├── reducer
    └── test

當(dāng)你通過功能特性來將文件分組,你可以避免在文件列表上下滾動,查找編輯所需要的文件這種情況。

6.利用判斷true值的方式來編寫代碼

要做出確定的斷言,避免使用溫順、無色、猶豫的語句,必要時使用 not 來否定、拒絕。典型的

  • isFlying優(yōu)于isNotFlying
  • late優(yōu)于notOneTime

if語句

if (err) return reject(err);

// do something

優(yōu)于

if (!err) {
  // ... do something
} else {
  return reject(err);
}

三元判斷語句

{
  [Symbol.iterator]: iterator ? iterator : defaultIterator
}

優(yōu)于

{
  [Symbol.iterator]: (!iterator) ? defaultIterator : iterator 
}

恰當(dāng)?shù)氖褂梅穸?/h4>

有時候我們只關(guān)心一個變量是否缺失,如果通過判斷true值的方式來命名,我們得用!操作符來否定它。這種情況下使用 "not" 前綴和取反操作符不如使用否定語句直接。

  • if (missingValue)優(yōu)于if (!hasValue)
  • if (anonymous)優(yōu)于if (!user)
  • if (!isEmpty(thing))優(yōu)于if (notDefined(thing))

函數(shù)調(diào)用時,避免用null以及undefined代替某一個參數(shù)

不要在函數(shù)調(diào)用時,傳入undefined或者null作為某個參數(shù)的值。如果某些參數(shù)可以缺失,更推薦傳入一個對象:

const createEvent = ({
  title = 'Untitled',
  timeStamp = Date.now(),
  description = ''
}) => ({ title, description, timeStamp });

const birthdayParty = createEvent({
  title: 'Birthday Party',
  description: 'Best party ever!'
});

優(yōu)于

const createEvent = (
  title = 'Untitled',
  timeStamp = Date.now(),
  description = ''
) => ({ title, description, timeStamp });

const birthdayParty = createEvent(
  'Birthday Party',
  undefined, // This was avoidable
  'Best party ever!'  
);

不同的技術(shù)方案利用不同的代碼組織結(jié)構(gòu)來實現(xiàn)

迄今為止,應(yīng)用程序中未解決的問題很少。最終,我們都會一次又一次地做著同樣的事情。當(dāng)這樣的場景發(fā)生時,意味著代碼重構(gòu)的機(jī)會來啦。分辨出類似的部分,然后抽取出能夠支持每個不同部分的公共方法。這正是類庫以及框架為我們做的事情。

UI組件就是一個很好的例子。10 年前,使用 jQuery 寫出把界面更新、應(yīng)用邏輯和數(shù)據(jù)加載混在一起的代碼是再常見不過的。漸漸地,人們開始意識到我們可以將MVC應(yīng)用到客戶端的網(wǎng)頁上面,隨后,人們開始將model與UI更新邏輯分拆。

最終,web應(yīng)用廣泛采用組件化這一方案,這使得我們可以使用JSX或HTML模板來聲明式的對組件進(jìn)行建模。

最終,我們就能用完全相同的方式去表達(dá)所有組件的更新邏輯、生命周期,而不用再寫一堆命令式的代碼

對于熟悉組件的人,很容易看懂每個組件的原理:利用標(biāo)簽來表示UI元素,事件處理器用來觸發(fā)行為,以及用于添加回調(diào)的生命周期鉤子函數(shù),這些鉤子函數(shù)將在必要時運(yùn)行。

當(dāng)我們對于類似的問題采用類似的模式解決時,熟悉這個解決模式的人很快就能理解代碼是用來做什么的。

結(jié)論:代碼應(yīng)該簡單而不是過于簡單化

盡管在2015,ES6已經(jīng)標(biāo)準(zhǔn)化,但在2017,很多開發(fā)者仍然拒絕使用ES6特性,例如箭頭函數(shù),隱式return,rest以及spread操作符等等。利用自己熟悉的方式編寫代碼其實是一個幌子,這個說法是錯誤的。只有不斷嘗試,才能夠漸漸熟悉,熟悉之后,你會發(fā)現(xiàn)簡潔的ES6特性明顯優(yōu)于ES5:與語法結(jié)構(gòu)偏重的ES5相比,簡潔的es6的代碼很簡單。

代碼應(yīng)該簡單,而不是過于簡單化。

簡潔的代碼有以下優(yōu)勢:

  • 更少的bug可能性
  • 更容易去debug

但也有如下弊端:

  • 修復(fù)bug的成本更高
  • 有可能引用更多的bug
  • 打斷了正常開發(fā)的流程

簡潔的代碼同樣:

  • 更易寫
  • 更易讀
  • 更好去維護(hù)

清楚自己的目標(biāo),不要毫無頭緒。毫無頭緒只會浪費(fèi)時間以及精力。投入精力去訓(xùn)練,讓自己熟悉,去學(xué)習(xí)更好的編程方式,以及更有更有活力的代碼風(fēng)格。

代碼應(yīng)該簡單,而不是簡單化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,598評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,777評論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,149評論 4 61
  • 我出生在黃河邊,我就在黃河邊上長大。 成天里看著黃河不停息地流淌著,許多故事就那么隨著黃河流去了。 ...
    魏昊霖閱讀 385評論 25 4
  • 因為金馬獎的原因,特地看了《七月與安生》,那種文藝的氣息,一下子就吸引了我。不得不說,這個電影拍得真好,周冬雨、馬...
    雨中笛聲閱讀 238評論 1 3