關(guān)于重構(gòu)php代碼提高可讀性的例子

翻譯自文章 Examples Of Refactoring PHP Code For Better Readability

重構(gòu)代碼是指當(dāng)你重構(gòu)已有代碼時(shí)不改變其外部行為。更簡(jiǎn)單的說(shuō)你的目標(biāo)是在不改變?cè)泄δ艿那疤嵯掳?壞"的代碼變好匀哄。

網(wǎng)上有許多關(guān)于重構(gòu)代碼的指南蝌戒。但是,我發(fā)現(xiàn)很多人只是向你介紹了寫(xiě)好代碼和結(jié)構(gòu)的思想,并沒(méi)有向你展示如何去把你的"壞的"代碼重構(gòu)成"好的"代碼继准。他們可能會(huì)談及比較高級(jí)的理論像可讀性、擴(kuò)展性矮男、穩(wěn)定性移必、易于測(cè)試、減少?gòu)?fù)雜性之類(lèi)的昂灵,可是這些都是重構(gòu)過(guò)程中要達(dá)到的目標(biāo)避凝,他們并沒(méi)有向我們展示實(shí)際中這些情況的例子是怎么樣的。

在這篇文章中我不談?wù)撃闶裁磿r(shí)候需要重構(gòu)(個(gè)人認(rèn)為你需要做這些當(dāng)你遇到壞代碼時(shí))眨补,我也不談?wù)摓槭裁次覀冃枰貥?gòu)管削。我只想關(guān)注一些在重構(gòu)代碼中會(huì)遇到的,共同的撑螺、可以上手的原理含思,我也會(huì)給你一些像真實(shí)情況的代碼例子。在本文中我會(huì)使用php代碼(WordPress是用php寫(xiě)的)但是這些原理你可以應(yīng)用在任何語(yǔ)言。

不要重復(fù)你自己(DRY)

可能你最經(jīng)常聽(tīng)到的編程規(guī)范就是 不要重復(fù)你自己(DRY) 含潘。如果你發(fā)現(xiàn)你自己把相同的代碼重復(fù)了幾次饲做,那你就應(yīng)該把功能封裝到它所屬的類(lèi)或方法中然后使用這個(gè)類(lèi)或方法避免重復(fù)的代碼。這意味著當(dāng)你幾個(gè)月后代碼出了問(wèn)題只需要修改一個(gè)地方的代碼就可以了遏弱。
一個(gè)很好的例子是盆均,當(dāng)兩個(gè)不同但相似的類(lèi)需要一些相同的功能,你應(yīng)該創(chuàng)建一個(gè) abstract class 然后讓這兩個(gè)類(lèi)繼承這個(gè)abstract class而不是在兩個(gè)類(lèi)當(dāng)中重復(fù)代碼漱逸。

修改前例子:
<?php
class AwesomeAddon {

    private $settings;
    public function __construct( $settings ) {
        $this->set_settings( $settings );
    }
    protected function set_settings( $settings ) {
        if ( ! is_array( $settings ) ) {
            throw new \Exception( 'Invalid settings' );
        }
        $this->settings = $settings;
    }
    
    protected function do_something_awesome() {
        //...
    }
}
class EvenMoreAwesomeAddon {

    private $settings;
    public function __construct( $settings ) {
        $this->set_settings( $settings );
    }
    protected function set_settings( $settings ) {
        if ( ! is_array( $settings ) ) {
            throw new \Exception( 'Invalid settings' );
        }
        $this->settings = $settings;
    }
    
    protected function do_something_even_more_awesome() {
        //...
    }
} 
修改后代碼:
<?php
abstract class Addon {
    protected $settings;
    protected function set_settings( $settings ) {
        if ( ! is_array( $settings ) ) {
            throw new \Exception( 'Invalid settings' );
        }
        
        $this->settings = $settings;
    }
}
class AwesomeAddon extends Addon {
    public function __construct( $settings ) {
        $this->set_settings( $settings );
    }
    
    protected function do_something_awesome() {
        //...
    }
}
class EvenMoreAwesomeAddon extends Addon {
    public function __construct( $settings ) {
        $this->set_settings( $settings );
    }
    
    protected function do_something_even_more_awesome() {
        //...
    }
}

這只是個(gè)簡(jiǎn)單的關(guān)于構(gòu)造代碼避免重復(fù)的例子泪姨。

分解復(fù)雜函數(shù)

復(fù)雜的函數(shù)或方法是編程中另一個(gè)會(huì)導(dǎo)致技術(shù)負(fù)債和難以閱讀的原因。

"程序是寫(xiě)給人看的饰抒,然后順便讓機(jī)器執(zhí)行" —— Harold Abelson

讓別人能讀懂和理解你寫(xiě)的代碼是最重要的事肮砾,所以讓復(fù)雜函數(shù)變得好理解的方式是把它拆成更小的、更容易理解的小塊袋坑。

下面是一個(gè)復(fù)雜的函數(shù)仗处。不要擔(dān)心你看不懂他在寫(xiě)什么,你只要看一眼他有多復(fù)雜就可以了枣宫。

<?php
function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $force_new_s3_client = false, $remove_local_files = true ) {
    $return_metadata = null;
    if ( is_null( $data ) ) {
        $data = wp_get_attachment_metadata( $post_id, true );
    } else {
        // As we have passed in the meta, return it later
        $return_metadata = $data;
    }
    if ( is_wp_error( $data ) ) {
        return $data;
    }
    // Allow S3 upload to be hijacked / cancelled for any reason
    $pre = apply_filters( 'as3cf_pre_upload_attachment', false, $post_id, $data );
    if ( false !== $pre ) {
        if ( ! is_null( $return_metadata ) ) {
            // If the attachment metadata is supplied, return it
            return $data;
        }
        $error_msg = is_string( $pre ) ? $pre : __( 'Upload aborted by filter \'as3cf_pre_upload_attachment\'', 'amazon-s3-and-cloudfront' );
        return $this->return_upload_error( $error_msg );
    }
    if ( is_null( $file_path ) ) {
        $file_path = get_attached_file( $post_id, true );
    }
    // Check file exists locally before attempting upload
    if ( ! file_exists( $file_path ) ) {
        $error_msg = sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $file_path );
        return $this->return_upload_error( $error_msg, $return_metadata );
    }
    $file_name     = basename( $file_path );
    $type          = get_post_mime_type( $post_id );
    $allowed_types = $this->get_allowed_mime_types();
    // check mime type of file is in allowed S3 mime types
    if ( ! in_array( $type, $allowed_types ) ) {
        $error_msg = sprintf( __( 'Mime type %s is not allowed', 'amazon-s3-and-cloudfront' ), $type );
        return $this->return_upload_error( $error_msg, $return_metadata );
    }
    $acl = self::DEFAULT_ACL;
    // check the attachment already exists in S3, eg. edit or restore image
    if ( ( $old_s3object = $this->get_attachment_s3_info( $post_id ) ) ) {
        // use existing non default ACL if attachment already exists
        if ( isset( $old_s3object['acl'] ) ) {
            $acl = $old_s3object['acl'];
        }
        // use existing prefix
        $prefix = dirname( $old_s3object['key'] );
        $prefix = ( '.' === $prefix ) ? '' : $prefix . '/';
        // use existing bucket
        $bucket = $old_s3object['bucket'];
        // get existing region
        if ( isset( $old_s3object['region'] ) ) {
            $region = $old_s3object['region'];
        };
    } else {
        // derive prefix from various settings
        if ( isset( $data['file'] ) ) {
            $time = $this->get_folder_time_from_url( $data['file'] );
        } else {
            $time = $this->get_attachment_folder_time( $post_id );
            $time = date( 'Y/m', $time );
        }
        $prefix = $this->get_file_prefix( $time );
        // use bucket from settings
        $bucket = $this->get_setting( 'bucket' );
        $region = $this->get_setting( 'region' );
        if ( is_wp_error( $region ) ) {
            $region = '';
        }
    }
    $acl = apply_filters( 'as3cf_upload_acl', $acl, $data, $post_id );
    $s3object = array(
        'bucket' => $bucket,
        'key'    => $prefix . $file_name,
        'region' => $region,
    );
    // store acl if not default
    if ( $acl != self::DEFAULT_ACL ) {
        $s3object['acl'] = $acl;
    }
    $s3client = $this->get_s3client( $region, $force_new_s3_client );
    $args = array(
        'Bucket'       => $bucket,
        'Key'          => $prefix . $file_name,
        'SourceFile'   => $file_path,
        'ACL'          => $acl,
        'ContentType'  => $type,
        'CacheControl' => 'max-age=31536000',
        'Expires'      => date( 'D, d M Y H:i:s O', time() + 31536000 ),
    );
    $args = apply_filters( 'as3cf_object_meta', $args, $post_id );
    $files_to_remove = array();
    if ( file_exists( $file_path ) ) {
        $files_to_remove[] = $file_path;
        try {
            $s3client->putObject( $args );
        } catch ( Exception $e ) {
            $error_msg = sprintf( __( 'Error uploading %s to S3: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() );
            return $this->return_upload_error( $error_msg, $return_metadata );
        }
    }
    delete_post_meta( $post_id, 'amazonS3_info' );
    add_post_meta( $post_id, 'amazonS3_info', $s3object );
    $file_paths        = $this->get_attachment_file_paths( $post_id, true, $data );
    $additional_images = array();
    $filesize_total             = 0;
    $remove_local_files_setting = $this->get_setting( 'remove-local-file' );
    if ( $remove_local_files_setting ) {
        $bytes = filesize( $file_path );
        if ( false !== $bytes ) {
            // Store in the attachment meta data for use by WP
            $data['filesize'] = $bytes;
            if ( is_null( $return_metadata ) ) {
                // Update metadata with filesize
                update_post_meta( $post_id, '_wp_attachment_metadata', $data );
            }
            // Add to the file size total
            $filesize_total += $bytes;
        }
    }
    foreach ( $file_paths as $file_path ) {
        if ( ! in_array( $file_path, $files_to_remove ) ) {
            $additional_images[] = array(
                'Key'        => $prefix . basename( $file_path ),
                'SourceFile' => $file_path,
            );
            $files_to_remove[] = $file_path;
            if ( $remove_local_files_setting ) {
                // Record the file size for the additional image
                $bytes = filesize( $file_path );
                if ( false !== $bytes ) {
                    $filesize_total += $bytes;
                }
            }
        }
    }
    if ( $remove_local_files ) {
        if ( $remove_local_files_setting ) {
            // Allow other functions to remove files after they have processed
            $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path );
            // Remove duplicates
            $files_to_remove = array_unique( $files_to_remove );
            // Delete the files
            $this->remove_local_files( $files_to_remove );
        }
    }
    // Store the file size in the attachment meta if we are removing local file
    if ( $remove_local_files_setting ) {
        if ( $filesize_total > 0 ) {
            // Add the total file size for all image sizes
            update_post_meta( $post_id, 'wpos3_filesize_total', $filesize_total );
        }
    } else {
        if ( isset( $data['filesize'] ) ) {
            // Make sure we don't have a cached file sizes in the meta
            unset( $data['filesize'] );
            if ( is_null( $return_metadata ) ) {
                // Remove the filesize from the metadata
                update_post_meta( $post_id, '_wp_attachment_metadata', $data );
            }
            delete_post_meta( $post_id, 'wpos3_filesize_total' );
        }
    }
    if ( ! is_null( $return_metadata ) ) {
        // If the attachment metadata is supplied, return it
        return $data;
    }
    return $s3object;
}

如果整個(gè)函數(shù)變成下面這樣會(huì)更好理解

<?php
function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $force_new_s3_client = false, $remove_local_files = true ) {
    $return_metadata = $this->get_attachment_metadata( $post_id );
    if ( is_wp_error( $return_metadata ) ) {
        return $return_metadata;
    }
    // Allow S3 upload to be hijacked / cancelled for any reason
    $pre = apply_filters( 'as3cf_pre_upload_attachment', false, $post_id, $data );
    if ( $this->upload_should_be_cancelled( $pre ) ) {
        return $pre;
    }
    // Check file exists locally before attempting upload
    if ( ! $this->local_file_exists() ) {
        $error_msg = sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $file_path );
        
        return $this->return_upload_error( $error_msg, $return_metadata );
    }
    // check mime type of file is in allowed S3 mime types
    if ( ! $this->is_valid_mime_type() ) {
        $error_msg = sprintf( __( 'Mime type %s is not allowed', 'amazon-s3-and-cloudfront' ), $type );
        return $this->return_upload_error( $error_msg, $return_metadata );
    }
    $s3object = $this->get_attachment_s3_info( $post_id );
    $acl = $this->get_s3object_acl( $s3object );
    $s3client = $this->get_s3client( $region, $force_new_s3_client );
    $args = array(
        'Bucket'       => $s3object['bucket'],
        'Key'          => $s3object['key'],
        'SourceFile'   => $s3object['source_file'],
        'ACL'          => $acl,
        'ContentType'  => $s3object['mime_type'],
        'CacheControl' => 'max-age=31536000',
        'Expires'      => date( 'D, d M Y H:i:s O', time() + 31536000 ),
    );
    
    $s3client->putObject( $args );
    $this->maybe_remove_files( $args, $s3object );
    return $s3object;
}

這是很好閱讀和理解的婆誓。簡(jiǎn)單的把大塊的代碼分成小的代碼庫(kù)是很好的。

有個(gè)事情需要記住的是镶柱,不要擔(dān)心你用很長(zhǎng)的名字當(dāng)方法名旷档,記住你的目標(biāo)是可讀性,所以如果只用簡(jiǎn)單的名字命名的話(huà)它會(huì)讓你的代碼變得很難以理解歇拆。舉個(gè)例子:

$this->get_att_inf( $post_id );

比下面的代碼難以理解:

$this->get_attachment_s3_info( $post_id );

分解復(fù)雜的條件

你應(yīng)該見(jiàn)過(guò)像下面這樣條件很長(zhǎng)的例子:

<?php
if ( isset( $settings['wp-uploads'] ) && $settings['wp-uploads'] && in_array( $key, array( 'copy-to-s3', 'serve-from-s3' ) ) ) {
    return '1';
}

帶條件的長(zhǎng)段代碼很難閱讀和理解鞋屈。一個(gè)簡(jiǎn)單的解決方案是將條件代碼提取為明確命名的方法。例如:

<?php
if ( upload_is_valid( $settings, $key ) ) {
    return '1';
}
function upload_is_valid( $settings, $key ) {
    return isset( $settings['wp-uploads'] ) && $settings['wp-uploads'] && in_array( $key, array( 'copy-to-s3', 'serve-from-s3' ) );
}

這使得你的代碼對(duì)于以后的維護(hù)者來(lái)說(shuō)更容易理解故觅。只要條件方法是明確命名的厂庇,不用看代碼也很容易理解它是做什么的。這種做法也被稱(chēng)為聲明式編程输吏。

用守衛(wèi)子句GUARD CLAUSES替代嵌套條件

另一種重構(gòu)復(fù)雜條件的方法是使用所謂的“守衛(wèi)子句”权旷。 Guard子句簡(jiǎn)單地提取所有導(dǎo)致調(diào)用異常或立即從方法返回值的條件贯溅,把它放在方法的開(kāi)始位置拄氯。例如:

<?php
function get_setting( $key, $default = '' ) {
    $settings = $this->get_settings();
    
    // If legacy setting set, migrate settings
    if ( isset( $settings['wp-uploads'] ) && $settings['wp-uploads'] && in_array( $key, array( 'copy-to-s3', 'serve-from-s3' ) ) ) {
        return $default;
    } else {
        // Turn on object versioning by default
        if ( 'object-versioning' == $key && ! isset( $settings['object-versioning'] ) ) {
            return $default;
        } else {
            // Default object prefix
            if ( 'object-prefix' == $key && ! isset( $settings['object-prefix'] ) ) {
                return $this->get_default_object_prefix();
            } else {
                if ( 'use-yearmonth-folders' == $key && ! isset( $settings['use-yearmonth-folders'] ) ) {
                    return get_option( 'uploads_use_yearmonth_folders' );
                } else {
                    $value = parent::get_setting( $key, $default );
                    return apply_filters( 'as3cf_setting_' . $key, $value );
                }
            }
        }
    }
    
    return $default;
}

這里你可以看到如果方法變得更復(fù)雜你可以多快結(jié)束"條件地獄"。但是如果你使用守衛(wèi)子句來(lái)重構(gòu)方法它浅,它將變成下面這樣:

<?php
function get_setting( $key, $default = '' ) {
    $settings = $this->get_settings();
    
    // If legacy setting set, migrate settings
    if ( isset( $settings['wp-uploads'] ) && $settings['wp-uploads'] && in_array( $key, array( 'copy-to-s3', 'serve-from-s3' ) ) ) {
        return $default;
    }
    
    // Turn on object versioning by default
    if ( 'object-versioning' == $key && ! isset( $settings['object-versioning'] ) ) {
        return $default;
    }
    
    // Default object prefix
    if ( 'object-prefix' == $key && ! isset( $settings['object-prefix'] ) ) {
        return $this->get_default_object_prefix();
    }
    
    // Default use year and month folders
    if ( 'use-yearmonth-folders' == $key && ! isset( $settings['use-yearmonth-folders'] ) ) {
        return get_option( 'uploads_use_yearmonth_folders' );
    }
    $value = parent::get_setting( $key, $default );
    
    return apply_filters( 'as3cf_setting_' . $key, $value );
}

現(xiàn)在即使方法變得復(fù)雜译柏,一段時(shí)間后他也不會(huì)變成維護(hù)的難題。

使用函數(shù)方法重構(gòu)循環(huán)和條件

這是一個(gè)比較高級(jí)的重構(gòu)方法姐霍,它被大量使用在函數(shù)式編程和類(lèi)庫(kù)中(這種類(lèi)型的方法在javascript領(lǐng)域中經(jīng)常使用)鄙麦。你可能有聽(tīng)說(shuō)過(guò)mapreduce也想知道他們是什么并且如何使用典唇,事實(shí)證明這些方法可以大幅度提高你代碼的可讀性。

這個(gè)例子的靈感來(lái)自Adam Wathan關(guān)于這個(gè)主題的很棒的視頻(你應(yīng)該看看他即將出版的書(shū))胯府,基于Laravel Collections介衔。不過(guò),我已經(jīng)調(diào)整了該示例骂因,基于標(biāo)準(zhǔn)PHP函數(shù)炎咖。

讓我們看看兩個(gè)常見(jiàn)的場(chǎng)景,并看看如何使用函數(shù)方法來(lái)改進(jìn)它們寒波。我們的示例從API獲取一堆$events塘装,然后根據(jù)事件類(lèi)型計(jì)算得分:

<?php

$events = file_get_contents( 'https://someapi.com/events' );
$types = array();
foreach ( $events as $event ) {
    $types[] = $event->type;
}
$score = 0;
foreach ( $types as $type ) {
    switch ( $type ) {
        case 'type1':
            $score += 2;
            break;
        case 'type2':
            $score += 5;
            break;
        case 'type3':
            $score += 10;
            break;
        default:
            $score += 1;
            break;
    }
}

我們可以改進(jìn)的第一件事是用map函數(shù)替換foreach循環(huán)。當(dāng)你想從現(xiàn)有的數(shù)組中創(chuàng)建一個(gè)新的數(shù)組時(shí)影所,可以使用map函數(shù)。在我們的例子中僚碎,我們從$events數(shù)組創(chuàng)建一個(gè)$ types數(shù)組猴娩。 PHP有一個(gè)array_map函數(shù),可以讓我們把上面的第一個(gè)foreach編寫(xiě)成如下所示:

<?php
$types = array_map( function( $event ) {
    return $event->type;
}, $events );

提示:要使用匿名函數(shù)勺阐,需要的php版本為卷中,PHP 5.3+

我們可以做的第二件事情是,把大的switch語(yǔ)句分解渊抽,讓它更簡(jiǎn)單的進(jìn)行下去

<?php
$scores = array(
    'type1' => 2,
    'type2' => 5,
    'type3' => 10,
);
$score = 0;
foreach ( $types as $type ) {
    $score += isset( $scores[$type] ) ? $scores[$type] : 1;
}

實(shí)際上我們可以走的更遠(yuǎn)一步蟆豫,通過(guò)php的 array_reduce 方法在單個(gè)方法內(nèi)計(jì)算$score的值。一個(gè)reduce方法接收一個(gè)數(shù)組的值然后把他們歸成一個(gè)值:

<?php
$scores = array(
    'type1' => 2,
    'type2' => 5,
    'type3' => 10,
);
$score = array_reduce( $types, function( $result, $type ) use ( $scores ) {
    return $result += isset( $scores[$type] ) ? $scores[$type] : 1;
} );

把現(xiàn)在有的代碼合并起來(lái)

<?php
$events = file_get_contents( 'https://someapi.com/events' );
$types = array_map( function( $event ) {
    return $event->type;
}, $events );
$scores = array(
    'type1' => 2,
    'type2' => 5,
    'type3' => 10,
);
$score = array_reduce( $types, function( $result, $type ) use ( $scores ) {
    return $result += isset( $scores[$type] ) ? $scores[$type] : 1;
} );

好多了懒闷∈酰看不到循環(huán)或則條件了。你可以想象愤估,如果獲取$types和計(jì)算$score變得更加復(fù)雜的話(huà)帮辟,那么在調(diào)用的方法里面重構(gòu)mapreduce方法是比較容易的。現(xiàn)在可以這樣做了玩焰,我們已經(jīng)降低復(fù)雜度到一個(gè)函數(shù)了由驹。

進(jìn)一步閱讀

我只是在這篇文章中講了重構(gòu)的表面知識(shí)。還有很多我可以談?wù)摰膬?nèi)容昔园,但是希望這可以讓你對(duì)重構(gòu)代碼的實(shí)際情況有個(gè)小的了解蔓榄。

如果你想深入了解,我推薦SourceMaking重構(gòu)指南默刚。它涵蓋了大量主題甥郑,并且每個(gè)主題都有一個(gè)示例,因此您可以準(zhǔn)確了解重構(gòu)羡棵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壹若,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌店展,老刑警劉巖养篓,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赂蕴,居然都是意外死亡柳弄,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)概说,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碧注,“玉大人,你說(shuō)我怎么就攤上這事糖赔∑钾ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵放典,是天一觀的道長(zhǎng)逝变。 經(jīng)常有香客問(wèn)我,道長(zhǎng)奋构,這世上最難降的妖魔是什么壳影? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮弥臼,結(jié)果婚禮上宴咧,老公的妹妹穿的比我還像新娘。我一直安慰自己径缅,他們只是感情好掺栅,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著芥驳,像睡著了一般柿冲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兆旬,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天假抄,我揣著相機(jī)與錄音,去河邊找鬼丽猬。 笑死宿饱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脚祟。 我是一名探鬼主播谬以,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼由桌!你這毒婦竟也來(lái)了为黎?” 一聲冷哼從身側(cè)響起邮丰,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铭乾,沒(méi)想到半個(gè)月后剪廉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡炕檩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年斗蒋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笛质。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泉沾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妇押,到底是詐尸還是另有隱情跷究,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布敲霍,位于F島的核電站揭朝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏色冀。R本人自食惡果不足惜痹届,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一海蔽、第九天 我趴在偏房一處隱蔽的房頂上張望邪狞。 院中可真熱鬧蛤迎,春花似錦薯嗤、人聲如沸买窟。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)嘉抓。三九已至索守,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抑片,已是汗流浹背卵佛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留敞斋,地道東北人截汪。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像植捎,于是被迫代替她去往敵國(guó)和親衙解。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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