基于Redux思想編寫(xiě)高度可測(cè)試的iOS代碼

測(cè)試驅(qū)動(dòng)開(kāi)發(fā)

1.什么是測(cè)試驅(qū)動(dòng)開(kāi)發(fā)祈纯?
基本思想就是在開(kāi)發(fā)功能代碼之前您觉,先編寫(xiě)測(cè)試代碼,然后只編寫(xiě)使測(cè)試通過(guò)的功能代碼庐橙,從而以測(cè)試來(lái)驅(qū)動(dòng)整個(gè)開(kāi)發(fā)過(guò)程的進(jìn)行。這有助于編寫(xiě)簡(jiǎn)潔可用和高質(zhì)量的代碼借嗽,有很高的靈活性和健壯性态鳖,能快速響應(yīng)變化,并加速開(kāi)發(fā)過(guò)程恶导。
我并不是說(shuō)以后寫(xiě)業(yè)務(wù)浆竭,都要先寫(xiě)單元測(cè)試,然后寫(xiě)完單元測(cè)試后開(kāi)始寫(xiě)業(yè)務(wù)惨寿。這會(huì)拉長(zhǎng)開(kāi)發(fā)周期邦泄,再說(shuō)老板也不答應(yīng)呀。但我一直認(rèn)為高度可測(cè)試的代碼的可維護(hù)性更強(qiáng)裂垦。所以可以在平時(shí)寫(xiě)代碼前顺囊,多想想為自己的代碼添加單元測(cè)試麻煩嗎,是不是還有可測(cè)試性更高的寫(xiě)法缸废。
但往往我們?cè)诮o自己的App添加單元測(cè)式包蓝,如果在寫(xiě)代碼的時(shí)候沒(méi)有過(guò)多的思考,寫(xiě)了很多膠水代碼企量。編寫(xiě)寫(xiě)單元測(cè)試會(huì)變得很困難测萎。無(wú)論是UI測(cè)試還是功能測(cè)試。如果改UI的代碼散落各處届巩,一個(gè)方法受狀態(tài)的影響太多硅瞧。
而且狀態(tài)可能來(lái)自服務(wù)器(http請(qǐng)求的回調(diào)),用戶(hù)操作(Target - Action)恕汇,一個(gè)方法依賴(lài)的外部狀態(tài)太多了腕唧,要寫(xiě)單元測(cè)試得用一些測(cè)試庫(kù)如ocmork,來(lái)模擬一些具體的依賴(lài)類(lèi)瘾英,模擬相應(yīng)或者返回枣接,而且還的配合用戶(hù)的點(diǎn)擊狀態(tài)。那么寫(xiě)一個(gè)函數(shù)的測(cè)試變的很復(fù)雜缺谴。而且一個(gè)VC如果很多都由這種代碼組成但惶,重構(gòu)困難,加功能困難,需要改時(shí)候容易牽一發(fā)動(dòng)全身膀曾,這個(gè)類(lèi)也變得難以維護(hù)县爬。

2.設(shè)計(jì)階段多思考可測(cè)試性
如果一個(gè)模塊(大模塊或者一個(gè)MVC模塊)變得容易測(cè)試,覆蓋范圍廣添谊,可維護(hù)性一定強(qiáng)财喳。對(duì)于一個(gè)函數(shù)或者方法,怎么樣才比較容易測(cè)試斩狱,測(cè)試無(wú)非就是模擬輸入局扶,驗(yàn)證輸出怠苔。如果一個(gè)方法和輸入和輸出明確少漆,類(lèi)似于y= f(x),那么我們只要模擬x题禀,驗(yàn)證y卡睦。就可以完成這個(gè)函數(shù)的單元測(cè)試悍手。而這個(gè)f(x)的內(nèi)部實(shí)現(xiàn)不會(huì)依賴(lài)于函數(shù)外部的參數(shù)证薇,也就是說(shuō)這個(gè)f(x)是獨(dú)立的桥氏,沒(méi)有副作用的函數(shù)乍赫。這個(gè)理想的函數(shù)有純函數(shù)的一些特性瓣蛀。那么什么是純函數(shù)?

3.純函數(shù)
指一個(gè)函數(shù)如果有相同的輸入雷厂,則它產(chǎn)生相同的輸出惋增,一旦輸入給定,那么輸出則唯一確定改鲫,沒(méi)有負(fù)作用诈皿。比如y = sin(X)。
像我們平時(shí)寫(xiě)的[someInstance method];這樣的函數(shù)由于不存在輸入和輸出像棘,如果內(nèi)部實(shí)現(xiàn)還對(duì)外部的結(jié)構(gòu)參數(shù)做一些改變稽亏,就很難直接測(cè)試這個(gè)函數(shù)。函數(shù)內(nèi)部如果對(duì)參數(shù)意外的變量或者狀態(tài)進(jìn)行改變缕题,這個(gè)就是這個(gè)函數(shù)的副作用(side effect)截歉,也可以說(shuō)這個(gè)函數(shù)對(duì)其他變量的依賴(lài)性強(qiáng),而且單看這個(gè)函數(shù)本身烟零,還預(yù)見(jiàn)不了這個(gè)改變瘪松,依賴(lài)被函數(shù)所屏蔽了。這是一個(gè)容易引起bug的潛在因素锨阿。

4.純函數(shù)特點(diǎn)
先偏離App宵睦,純函數(shù)編程有以下特點(diǎn):
4.1.函數(shù)可以被當(dāng)成參數(shù)和output。
4.2.函數(shù)的結(jié)果只受函數(shù)參數(shù)影響墅诡,不依賴(lài)于定義在函數(shù)外的變量壳嚎,對(duì)于特定輸入有特定輸出
4.3.依賴(lài)于不可變數(shù)據(jù)結(jié)構(gòu)
4.4.由于計(jì)算是透明的,任何時(shí)候執(zhí)行產(chǎn)生相同結(jié)果,可以推遲計(jì)算诬辈,知道需要的時(shí)候酵使。lazy load 或者 swift的copy on write
4.5利用范型進(jìn)行高度抽象成功能性程序。
4.6.函數(shù)為一等公民焙糟,可以作為參數(shù)和返回值口渔。而且還可以延遲執(zhí)行。

其實(shí)Swift是一個(gè)很好實(shí)踐函數(shù)式編程的一門(mén)得力語(yǔ)言穿撮,但我們今天不講swift的函數(shù)式編程缺脉,如果對(duì)Swift的函數(shù)式編程思想感興趣,可以參考objc.io上的advanced-swiftfunctional-swift悦穿,我這里有中文的譯本攻礼。

5.實(shí)踐
5.1 如果你們和我一樣現(xiàn)在還在用OC,其實(shí)作為一個(gè)純面向?qū)ο蟮木幊陶Z(yǔ)言栗柒,但編程的時(shí)候以函數(shù)式思想編程還是有借鑒意義的礁扮。可以在這里找到具體例子demo瞬沦。在介紹具體的代碼前太伊,我想先介紹以下Redux:

Redux由Dan Abramov在2015年創(chuàng)建。是受2014年Facebook的Flux架構(gòu)以及函數(shù)式編程語(yǔ)言Elm啟發(fā)逛钻。Redux因其簡(jiǎn)單易學(xué)體積小在短時(shí)間內(nèi)成為最熱門(mén)的前端架構(gòu)僚焦。

它有以下幾個(gè)核心概念:

Action:簡(jiǎn)單地,Actions就是事件曙痘。用戶(hù)的操作芳悲,網(wǎng)絡(luò)的回調(diào),Actions傳遞來(lái)自(用戶(hù)接口边坤,內(nèi)部事件比如API調(diào)用和表單提交)的數(shù)據(jù)給store名扛。store只獲取來(lái)自Actions的信息。
Store:Store對(duì)象保存應(yīng)用的狀態(tài)并提供一些幫助方法來(lái)存取狀態(tài)茧痒,分發(fā)狀態(tài)以及注冊(cè)監(jiān)聽(tīng)罢洲。全部state由一個(gè)store來(lái)表示。任何action通過(guò)reducer返回一個(gè)新的狀態(tài)對(duì)象文黎。這就使得Redux非常簡(jiǎn)單以及可預(yù)測(cè)惹苗。
Reducer:在Redux中,reducer就是獲得這個(gè)應(yīng)用的當(dāng)前狀態(tài)和事件然后返回一個(gè)新?tīng)顟B(tài)的函數(shù)耸峭。(Action,State) ---> State
State:應(yīng)用的狀態(tài)桩蓉,決定著應(yīng)用的行為和輸出。

對(duì)于 app 而言劳闹,我們總是會(huì)和一定的用戶(hù)輸入打交道院究,也必然會(huì)需要按照用戶(hù)的輸入和已知狀態(tài)來(lái)更新 UI 作為“輸出”洽瞬。這個(gè)狀態(tài)我們可以抽象成State,用戶(hù)的輸入或者其他能夠改變狀態(tài)的行為我們抽象為Action业汰,那我們需要寫(xiě)自己的Reduce函數(shù)伙窃。設(shè)計(jì)Reduce函數(shù)的時(shí)候最好輸入給一個(gè)state,輸出一個(gè)全新的newState样漆,它們是不同的對(duì)象为障,而不僅僅只是在同一個(gè)對(duì)象的基礎(chǔ)上進(jìn)行改變,這樣在訂閱方才可以明確知道state是否發(fā)生改變放祟。

reducer(Action,State) -> State

我們還需要一個(gè)State來(lái)驅(qū)動(dòng)變化鳍怨,所以我認(rèn)為State結(jié)構(gòu)內(nèi)部可以引用一些驅(qū)動(dòng)用戶(hù)行為或者UI變化的數(shù)據(jù)源,最好確保 State 中每個(gè)節(jié)點(diǎn)都是 Immutable 的跪妥,這樣將確保 State 的消費(fèi)者在判斷數(shù)據(jù)是否變化時(shí)鞋喇,只要簡(jiǎn)單地進(jìn)行引用比較即可。

我們需要Store來(lái)存儲(chǔ)State眉撵,訂閱觀察者侦香,給State派發(fā)Action,所以一個(gè)Store可能抽象成這樣

@interface Store : NSObject
//當(dāng)前狀態(tài)
@property (nonatomic, strong,readonly) id<StateType> state;
//初始化方法纽疟,接受一個(gè)reducer和初始狀態(tài)
- (instancetype)initWithReducer:(Reducer )reducer
                   initialState:(id<StateType>)state;
//訂閱一個(gè)觀察者鄙皇,State發(fā)生改變通知觀察者
- (void)subscribeNext:(SubscribeBlock)subscriber;
//取消訂閱
- (void)unsubscribe;
//給State 派發(fā)Action
- (void)dispatch:(id<ActionType>)action;

@end

對(duì)于Action,生產(chǎn)者給Store dispatch Action仰挣。我們將Action分為同步的或者異步的,同步的Action通過(guò)Reduce產(chǎn)生新的State驅(qū)動(dòng)subscriber缠沈,異步的Action通過(guò)Reduce膘壶,這時(shí)候并不產(chǎn)生新的state,而是在回調(diào)中再向Store dispatch newAction 洲愤,再產(chǎn)生新的State后才驅(qū)動(dòng)subsriber颓芭。
數(shù)據(jù)的流動(dòng)就變成這樣:

數(shù)據(jù)傳遞.png

解釋?zhuān)篠tore會(huì)持有State和reducer,外界如果想要觸發(fā)新的State只有通過(guò)向Store派發(fā)Action柬赐,Store拿到Action和當(dāng)前的State亡问,會(huì)嘗試通過(guò)Reudcer產(chǎn)生一個(gè)新的State,如果這個(gè)Action是同步的肛宋,那么reduce可以立即產(chǎn)生新的有效的State州藕,然后通知訂閱者,訂閱者根據(jù)最新的State來(lái)決定UI的樣式酝陈。如果Action是異步的床玻,reduce不會(huì)立即產(chǎn)生一個(gè)newState,而是在異步操作的回調(diào)中給Store派發(fā)一個(gè)新的同步的Action沉帮。外界任何其他角色不直接改變UI锈死,UI是由唯一的State所決定贫堰。這樣要測(cè)試這部分的業(yè)務(wù),我們只要在給Store派發(fā)可預(yù)見(jiàn)的Action待牵,然后在Subscriber中檢測(cè)輸出其屏。這套邏輯本身沒(méi)有依賴(lài)其他任何的UI狀態(tài),所以單元測(cè)試變得簡(jiǎn)單缨该。

看了這么多抽象的邏輯我們看具體的demo

這是一個(gè)查詢(xún)省的一個(gè)demo偎行,跳轉(zhuǎn)后,會(huì)用coreData記錄下查詢(xún)記錄压彭,搜索部分輸入省名還可以進(jìn)行查詢(xún)睦优。

DateFlow.gif

我們看下Store類(lèi)的結(jié)構(gòu),Store初始化時(shí)候需要intialState和Reducer函數(shù),reducer要從外面?zhèn)魅氲脑蚴亲巢唬瑀educer要操作具體的State汗盘,這個(gè)State必定和業(yè)務(wù)綁定。為了解耦询一。值得一提的就是這個(gè)dispatch方法隐孽,store將訂閱者放到一個(gè)array容器里,接受到異步action的時(shí)候reducer會(huì)返回nil健蕊,我們就不通知訂閱者菱阵,否則執(zhí)行array的blocks

@interface Store : NSObject

@property (nonatomic, strong,readonly) id<StateType> state;

- (instancetype)initWithReducer:(Reducer )reducer
                   initialState:(id<StateType>)state;

- (void)subscribeNext:(SubscribeBlock)subscriber;
- (void)unsubscribe;
- (void)dispatch:(id<ActionType>)action;

@end
@implementation Store
...
- (void)dispatch:(id<ActionType>)action {
    id<StateType> previousState = _state;
    id<StateType> nextState = self.reducer(previousState,action);
    if (nextState) {
        self.state = nextState;
        if (self.subscribers.count > 0) {
            __weak __typeof(self)weakSelf = self;
            [self.subscribers enumerateObjectsUsingBlock:
      ^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                @synchronized (weakSelf) {
                    SubscribeBlock block = (SubscribeBlock)obj;
                    block(previousState,nextState);
                }
            }];
        }
    }
}
@end
...

在ViewController里面我們定義了State和Action的數(shù)據(jù)結(jié)構(gòu),當(dāng)然也可以將這兩部分抽出來(lái)放在另一個(gè)service類(lèi)中缩功,但我這里就這么做了晴及。

@interface State :NSObject<StateType,NSCopying>
@property (nonatomic, copy) NSArray *cities;
@property (nonatomic, copy) NSString *text;
@property (nonatomic, copy) NSArray *histories;

@end
@implementation State

- (id)copyWithZone:(NSZone *)zone {
    State *copy = [[[self class] allocWithZone:zone] init];
    copy.cities = self.cities;
    copy.text = self.text;
    copy.histories = self.histories;
    return copy;
}
@end

我認(rèn)為State就管理著數(shù)據(jù)源,因?yàn)镾tate決定著程序的行為和UI的樣式嫡锌,而一般這些都是一些特定的數(shù)據(jù)所驅(qū)動(dòng)的虑稼。這里整個(gè)demo的數(shù)據(jù)源有,所有省份(citites)势木,搜索的文字(text)和歷史記錄(history)三部分組成蛛倦,注意這里用的都是不可變得數(shù)據(jù)結(jié)構(gòu),state遵循了NSCopying協(xié)議啦桌,因?yàn)榻?jīng)過(guò)reducer的后的state和之前的state需要是兩個(gè)不同的State溯壶。

對(duì)于Action,區(qū)分了異步和同步的Action甫男,它們?cè)赗educer中的處理不一樣且改。

typedef NS_ENUM(NSUInteger, Action_Type) {
    UpdateText_Action,
    AddCities_Action,
    AddHistories_Action,
    
    //異步command
    FetchCities_Action,
    FetchHistories_Action,
    FetchAssociate_Action,
    ClearHistory_Action,
};
@interface Action :NSObject<ActionType>

@property (nonatomic, assign) Action_Type actionType;
@property (nonatomic, strong) id associateValues;
+ (instancetype)actionWithActionType:(Action_Type) type values:(id)associateValues;

@end

我們看這個(gè)重要的Reducer是如何被定義的,對(duì)于收到同步的AddHistories_Action板驳,我們?cè)O(shè)置屬性后返回新的State钾虐,對(duì)于異步的FetchHistories_Action,我們?cè)诨卣{(diào)中再發(fā)起一個(gè)新的同步的Action笋庄。

- (Reducer )reducer {
    __weak __typeof(self)weakSelf = self;
    Reducer reducer = ^(id<StateType> state, id<ActionType>action){
        State *previousState = (State *)state;
        State *currentState = [previousState copy];
        switch (action.actionType) {
.....省略一些代碼
            case AddHistories_Action:
            {
                id associateValue  = action.associateValues;
                currentState.histories = associateValue;
                break;
            }
            case FetchHistories_Action: {
                [FetchData fetchHistories:^(NSArray *data, NSError *error) {
                    Action *action = [Action new];
                    action.actionType = AddHistories_Action;
                    action.associateValues = data;
                    [weakSelf.store dispatch:action];//2
                }];
                currentState = nil;
                break;
            }  
           default:
                break;
        }
        return currentState;
    };
    
    return reducer;
}

我們?cè)趘iewDidload中我們1.初始化State效扫,2.初始化Store倔监,3.綁定訂閱者,4.發(fā)起查詢(xún)省的一個(gè)Action

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"clearHistory"
                         style:UIBarButtonItemStylePlain 
                        target:self 
                        action:@selector(clearHistory)];

      [self.textField addTarget:self 
                         action:@selector(changed:) 
                 forControlEvents:UIControlEventEditingChanged];
    
    //1.初始化State
    State *initialState = [[State alloc] init];
    //2.初始化Store
    _store = [[Store alloc] initWithReducer:self.reducer initialState:initialState];

    __weak __typeof(self)weakSelf = self;
    [_store subscribeNext:^(State *old ,State *new) {
    //3.綁定訂閱者
        [weakSelf stateDidChangeWithNew:new old:old];
    }];
//4.發(fā)起查詢(xún)省的一個(gè)Action
    Action *fetchCitiesAction = [Action actionWithActionType:FetchCities_Action values:nil];
    [_store dispatch:fetchCitiesAction];//3
    
}

那么在訂閱者受到通知后菌仁,我們就可以根據(jù)newState和oldState來(lái)決定UI樣式了,這樣我們就把對(duì)UI的管理集中在了一處浩习,外界只有通過(guò)向Store發(fā)送Action的形式才能改變State,而State又是唯一決定UI的元素济丘。那么代碼的邏輯是不是看起來(lái)就比較清晰了谱秽。

- (void)stateDidChangeWithNew:(State *)new old:(State *)old{
    
    NSLog(@"old = %@,new = %@",old.description,new.description);
    
    if (old.cities == nil || new.cities != old.cities) { 
//這里比較指針就好,因?yàn)榻?jīng)過(guò)reduce的是兩個(gè)不同的state摹迷,而且屬性都是不可變的疟赊。
        NSIndexSet *set = [[NSIndexSet alloc] initWithIndex:CitiesSection];
        [self.tableView reloadSections:set withRowAnimation:UITableViewRowAnimationFade];
    }
    
    if (old.histories == nil || new.histories != old.histories) { 
//這里比較指針就好,因?yàn)榻?jīng)過(guò)reduce的是兩個(gè)不同的state峡碉,而且屬性都是不可變的近哟。
        NSIndexSet *set = [[NSIndexSet alloc] initWithIndex:HistorySection];
        [self.tableView reloadSections:set withRowAnimation:UITableViewRowAnimationFade];
    }
//    [self.tableView reloadData];
    //update title
    if (new.text == nil) {
        self.title = @"省";
    } else {
        self.title = new.text;
    }
}

總結(jié)

其實(shí)這一套理論和語(yǔ)言本身無(wú)關(guān),和UI也沒(méi)有關(guān)系鲫寄,更像是一種設(shè)計(jì)思想吉执,我們可以把它用在任何無(wú)關(guān)UI的地方,有點(diǎn)向游戲設(shè)計(jì)中的狀態(tài)機(jī)的設(shè)計(jì)思路地来。但無(wú)論怎樣戳玫,這樣的代碼確實(shí)比習(xí)慣的膠水代碼可測(cè)試性更強(qiáng)。如果在Demo中我們需要添加一個(gè)新的邏輯未斑,我們只要在Action中添加一個(gè)新類(lèi)型咕宿,State里面加一個(gè)新的數(shù)據(jù)源,在reduce里面處理蜡秽,然后在stateDidChangeWithNew:old:處理府阀,代碼的邏輯依然清晰可見(jiàn)。如果喜歡請(qǐng)點(diǎn)個(gè)贊载城,或者star,Have Fun费就!??????

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诉瓦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子力细,更是在濱河造成了極大的恐慌睬澡,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眠蚂,死亡現(xiàn)場(chǎng)離奇詭異煞聪,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)逝慧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)昔脯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)啄糙,“玉大人,你說(shuō)我怎么就攤上這事云稚∷肀” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵静陈,是天一觀的道長(zhǎng)燕雁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鲸拥,這世上最難降的妖魔是什么拐格? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮刑赶,結(jié)果婚禮上捏浊,老公的妹妹穿的比我還像新娘。我一直安慰自己角撞,他們只是感情好呛伴,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著谒所,像睡著了一般热康。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上劣领,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天姐军,我揣著相機(jī)與錄音,去河邊找鬼尖淘。 笑死奕锌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的村生。 我是一名探鬼主播惊暴,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼趁桃!你這毒婦竟也來(lái)了辽话?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卫病,失蹤者是張志新(化名)和其女友劉穎油啤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蟀苛,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡益咬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帜平。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幽告。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梅鹦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出评腺,到底是詐尸還是另有隱情帘瞭,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布蒿讥,位于F島的核電站蝶念,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏芋绸。R本人自食惡果不足惜媒殉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摔敛。 院中可真熱鬧廷蓉,春花似錦、人聲如沸马昙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)行楞。三九已至攒暇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間子房,已是汗流浹背形用。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留证杭,地道東北人田度。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像解愤,于是被迫代替她去往敵國(guó)和親镇饺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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