P: Philosophy

本节中的规则非常笼统。
哲学规则总结:

  • P.1:直接在代码中表达想法
  • P.2:用ISO标准C ++编写代码
  • P.3:表达意图
  • P.4:理想情况下,程序应该是静态类型安全的
  • P.5:与运行时检查相比,更喜欢编译时检查
  • P.6:在编译时无法检查的内容应该在运行时检查
  • P.7:尽早发现运行时错误
  • P.8:不要泄漏任何资源
  • P.9:不要浪费时间或空间
  • P.10: 更喜欢不可变数据而不是可变数据
  • P.11:封装凌乱的结构,而不是遍布代码
  • P.12:适当使用支持工具
  • P.13:根据需要使用支持库
    哲学规则通常不是机械式的检查, 然而,个人规则反映了这些哲学的主题。 没有哲学基础,更具体/明确/可检查的规则就缺乏基本原理。

P.1:直接在代码中表达想法

Reason

编译器不读取注释(或设计文档),也不会读取许多程序员。 代码中表达的内容定义了语义,并且(原则上)可以由编译器和其他工具检查。

Example
class Date {
         // ...
    public:
        Month month() const;  // do
        int month();          // don't
        // ...
    };

month的第一个声明明确是关于返回a month而不是修改Date对象的状态。 第二个版本让读者猜测并为未捕获的错误打开更多可能性。

Example; bad

这个循环是std :: find的限制形式:

void f(vector<string>& v)
{
    string val;
    cin >> val;
    // ...
    int index = -1;                    // bad, plus should use gsl::index
    for (int i = 0; i < v.size(); ++i) {
        if (v[i] == val) {
            index = i;
            break;
        }
    }
    // ...
}
Example; good

意图更清晰的表达应该是:

void f(vector<string>& v)
{
    string val;
    cin >> val;
    // ...
    auto p = find(begin(v), end(v), val);  // better
    // ...
}

一个设计良好的库表达意图(what is to be done, rather than just how something is being done)远比直接使用语言功能更好。
C ++程序员应该了解标准库的基础知识,并在适当的地方使用它。 任何程序员都应该了解正在做的项目的基础库的知识,并在适当地方使用它们。 任何了解过这些指南的程序员都应该知道指南支持库,适时使用它在自己工作中或者学习当中。

Example
change_speed(double s);   // bad: what does s signify?
// ...
change_speed(2.3);

一个好方法是明确双重的意义(new speed or delta on old speed?) 和使用的单位:

change_speed(Speed s);    // better: the meaning of s is specified
// ...
change_speed(2.3);        // error: no unit
change_speed(23m / 10s);  // meters per second

我们本可以接受一个普通 (unit-less) double as a delta,但这很容易出错。 如果我们想要绝对速度和增量,我们就会定义一个Delta类型。

Enforcement

一般来说很难。

  • 习惯性使用const(检查成员函数是否修改其对象;检查函数是否修改指针或引用传递的参数)
  • cast是个标记((强制类型转换中和类型系统)
  • 检测模仿标准库的代码(hard)

P.2:用ISO标准C ++编写代码

Reason

这是一套编写ISO标准C ++的指南。

Note

存在需要扩展的环境,例如,访问系统资源。 在这种情况下,本地化使用必要的扩展并使用非核心编码指南控制它们的使用。 如果可能,构建封装扩展的接口,以便可以在不支持这些扩展的系统上关闭或编译它们。

扩展通常没有严格定义的语义。 即使是多个编译器常见且由多个编译器实现的扩展,也可能具有略微不同的行为和边缘情况行为,这是由于没有严格的标准定义。 充分利用任何此类扩展后,预计可移植性将受到影响。

Note

使用有效的ISO C ++并不能保证可移植性(更不用说正确性)了。 避免依赖于未定义的行为(e.g., undefined order of evaluation)并且注意具有实现定义含义的构造(e.g., sizeof(int))。

Note

存在必须限制使用标准C ++语言或库特征的环境,例如,以避免飞行器控制软件标准所要求的动态存储器分配。 在这种情况下,通过针对特定环境定制的这些编码指南的扩展来控制它们的(dis)使用。

Enforcement

使用最新的C ++编译器(当前为C ++ 17,C ++ 14或C ++ 11),其中包含一组不接受扩展的选项。

P.3:表达意图

Reason

除非声明某些代码的意图(例如,在名称或注释中),否则无法判断代码是否完成了应该执行的操作。

Example
gsl::index i = 0;
while (i < v.size()) {
    // ... do something with v[i] ...
}

这里没有表达“仅仅”循环v元素的意图。 索引的实现细节被公开(因此可能被滥用),并且i比循环的范围更长,这可能是也可能不是。 读者无法从这部分代码中了解到。

Better:
for (const auto& x : v) { /* do something with the value of x */ }

现在,没有明确提到迭代机制,并且循环操作对const元素的引用,以便不会发生意外修改。 如果需要修改,请说:

for (auto& x : v) { /* modify x */ }

有关for语句的更多详细信息,请参阅ES.71。 有时更好,使用命名算法:

for_each(v, [](int x) { /* do something with the value of x */ });
for_each(par, v, [](int x) { /* do something with the value of x */ });

最后一个变量清楚地表明我们对v的元素的处理顺序不感兴趣。
程序员应该熟悉:

Note

替代表述:说出应该做什么,而不仅仅是应该如何做。

Note

一些语言结构比其他语言结构表达意图更好

Example

如果两个ints意味着是2D点的坐标,那么说:

draw_line(int, int, int, int);  // obscure
draw_line(Point, Point);        // clearer
Enforcement

寻找有更好选择的常见模式:

  • simple for loops vs. range-for loops
    +f(T*, int) interfaces vs. f(span<T>) interfaces
  • 循环变量范围太大
  • 赤裸裸的new和delete
  • 具有许多内置类型参数的函数
    这里是聪明和高级程序转换的巨大区别所在。

P.4:理想情况下,程序应该是静态类型安全的

Reason

理想情况下,程序应该是完全静态(编译时)类型安全的。不幸的是,这是不可能的。问题:

  • unions
  • casts(数据类型转换)
  • array decay(阵列衰变)
  • range errors
  • narrowing conversions(缩小转换率)
Note

这些区域是严重问题的根源(例如,崩溃和安全违规)。 我们尝试提供替代技术。

Enforcement

我们可以根据需要单独禁止,限制或检测各个问题类别,并为各个程序提供可行性。 总是提出另一种选择。 例如:

  • unions -- use variant (in C++17)
  • casts -- 尽量减少使用; 模板可以帮助
  • array decay -- use span (from the GSL)
  • range errors -- use span
    +narrowing conversions -- 尽量减少使用,并在必要时使用narrownarrow_cast(来自GSL)

P.5:与运行时检查相比,更喜欢编译时检查

Reason

代码清晰度和高效。 您不需要为编译时捕获的错误编写错误处理程序。

Example
// Int is an alias used for integers
int bits = 0;         // don't: avoidable code
for (Int i = 1; i; i <<= 1)
    ++bits;
if (bits < 32)
    cerr << "Int too small\n";

这个例子无法实现它想要实现的目标(因为溢出是未定义的),应该用简单的替换static_assert:

// Int is an alias used for integers
static_assert(sizeof(Int) >= 4);    // do: compile-time check

或者更好的方法是使用类型系统并用Int32_t替换Int

Example
void read(int* p, int n);   // read max n integers into *p

int a[100];
read(a, 1000);    // bad, off the end
better
void read(span<int> r); // read into the range of integers r

int a[100];
read(a);        // better: let the compiler figure out the number of elements

替代配方:尽量把类型安全判断放在编译时候去做

Enforcement
  • 查找指针参数。
  • 查找运行时参数可能违规范围。

P.6:在编译时无法检查的内容应该在运行时检查

Reason

在程序中留下难以检测的错误会导致崩溃和糟糕的结果。

Note

理想情况下,我们在编译时或运行时捕获所有错误(不是程序员逻辑中的错误)。 在编译时捕获所有错误是不可能的,并且通常无法在运行时捕获所有剩余错误。 但是,如果有足够的资源(分析程序,运行时检查,机器资源,时间),我们应该努力编写原则上可以检查的程序。

Example, bad
// separately compiled, possibly dynamically loaded
extern void f(int* p);

void g(int n)
{
    // bad: the number of elements is not passed to f()
    f(new int[n]);
}

在这里,一些关键的信息(元素的数量)被完全"obscured"了,以至于静态分析可能变得不可行,当f()是ABI的一部分时,动态检查可能非常困难,因此我们无法"instrument" 指针。我们可以将有用的信息嵌入到免费存储中,但这需要对系统和编译器进行全局更改。这里的设计使得错误检测非常困难。

Example, bad

当然,我们可以通过指针传递元素的数量:

// separately compiled, possibly dynamically loaded
extern void f2(int* p, int n);

void g2(int n)
{
    f2(new int[n], m);  // bad: a wrong number of elements can be passed to f()
}

将元素的数量作为参数传递比仅传递指针并依赖于某些(未说明的)约定来更新(并且更常见),以便知道或发现元素的数量。 但是(如图所示),简单的拼写错误可能会引入严重的错误。 f2()的两个参数之间的连接是常规的,而不是显式的。
另外,f2()应该delete它的参数(或者调用者犯了第二个错误?)

Example, bad

标准库资源管理指针在指向对象时无法传递大小:

// separately compiled, possibly dynamically loaded
// NB: this assumes the calling code is ABI-compatible, using a
// compatible C++ compiler and the same stdlib implementation
extern void f3(unique_ptr<int[]>, int n);

void g3(int n)
{
    f3(make_unique<int[]>(n), m);    // bad: pass ownership and size separately
}
Example

我们需要将指针和元素数作为一个整体对象传递:

extern void f4(vector<int>&);   // separately compiled, possibly dynamically loaded
extern void f4(span<int>);      // separately compiled, possibly dynamically loaded
                                // NB: this assumes the calling code is ABI-compatible, using a
                                // compatible C++ compiler and the same stdlib implementation

void g3(int n)
{
    vector<int> v(n);
    f4(v);                     // pass a reference, retain ownership
    f4(span<int>{v});          // pass a view, retain ownership
}

这种设计将元素的数量作为对象的组成部分,假如错误是不可能的,在外部因素可以承担情况下,动态(运行时)总是进行动态检查。

Example

我们如何转移所有权和验证使用所需的所有信息?

vector<int> f5(int n)    // OK: move
{
    vector<int> v(n);
    // ... initialize v ...
    return v;
}

unique_ptr<int[]> f6(int n)    // bad: loses n
{
    auto p = make_unique<int[]>(n);
    // ... initialize *p ...
    return p;
}

owner<int*> f7(int n)    // bad: loses n and we might forget to delete
{
    owner<int*> p = new int[n];
    // ... initialize *p ...
    return p;
}
Example
  • ???
  • 展示如何通过传递多态基类的接口来避免可能的检查,当他们真正知道他们需要什么时? 或字符串作为 "free-style"选项
Enforcement
  • 标志(指针,计数)式接口(这将标记许多由于兼容性原因无法修复的示例)
  • ???

P.7: 尽早发现运行时错误

Reason

避免"mysterious"的崩溃。 避免导致(可能是无法识别的)错误结果。

Example
void increment1(int* p, int n)    // bad: error-prone
{
    for (int i = 0; i < n; ++i) ++p[i];
}

void use1(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment1(a, m);   // maybe typo, maybe m <= n is supposed
                        // but assume that m == 20
    // ...
}

这里我们在use1中犯了一个小错误,导致数据损坏或崩溃。 (指针,计数)式接口调用increment1(),没有现实的方法来防御超出范围的错误。 如果我们可以检查超出范围访问的下标,那么在访问p [10]之前不会发现错误。 我们可以提前检查并改进代码:

void increment2(span<int> p)
{
    for (int& x : p) ++x;
}

void use2(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment2({a, m});    // maybe typo, maybe m <= n is supposed
    // ...
}

现在,可以在定义的时候(早期)而不是稍后检查m <= n。 如果我们只有一个拼写错误,以便我们打算使用n作为界限,那么代码可以进一步简化(消除错误的可能性):

void use3(int m)
{
    const int n = 10;
    int a[n] = {};
    // ...
    increment2(a);   // the number of elements of a need not be repeated
    // ...
}
Example, bad

不要反复检查相同的值。 不要将结构化数据作为字符串传递:

Date read_date(istream& is);    // read date from istream

Date extract_date(const string& s);    // extract date from string

void user1(const string& date)    // manipulate date
{
    auto d = extract_date(date);
    // ...
}

void user2()
{
    Date d = read_date(cin);
    // ...
    user1(d.to_string());
    // ...
}

日期验证两次(由Date构造函数)并作为字符串(非结构化数据)传递。

Example

过度检查可能代价高昂。 有些情况下,早期检查是愚蠢的,因为您可能不需要该值,或者可能只需要比整体更容易检查的部分值。 同样,不要添加更改接口渐近性的有效性检查(例如,不要向平均复杂度为O(1)的接口添加O(n)检查。

class Jet {    // Physics says: e * e < x * x + y * y + z * z
    float x;
    float y;
    float z;
    float e;
public:
    Jet(float x, float y, float z, float e)
        :x(x), y(y), z(z), e(e)
    {
        // Should I check here that the values are physically meaningful?
    }

    float m() const
    {
        // Should I handle the degenerate case here?
        return sqrt(x * x + y * y + z * z - e * e);
    }

    ???
};

射频的物理定律(e * e <x * x + y * y + z * z)不是不变量,因为可能存在测量误差。

Enforcement
  • 查看指针和数组:尽早进行范围检查,而不是重复检查
  • 查看转化次数:消除或标记缩小的转化次数
  • 查找来自输入的未经检查的值
  • 查找结构化数据(具有不变量的类的对象)转换为字符串
  • ???

P.8:不要泄漏任何资源

Reason

随着时间的推移,即使资源的缓慢增长也会耗尽这些资源的可用性。 这对于长期运行的程序尤为重要,但却是负责任的编程行为的重要组成部分。

Example, bad
void f(char* name)
{
    FILE* input = fopen(name, "r");
    // ...
    if (something) return;   // bad: if something == true, a file handle is leaked
    // ...
    fclose(input);
}

Prefer RAII:

void f(char* name)
{
    ifstream input {name};
    // ...
    if (something) return;   // OK: no leak
    // ...
}
See also: The resource management section
Note

泄漏通俗地称为“任何未清理的东西”。 更重要的分类是“任何无法再清理的东西”。 例如,在堆上分配一个对象,然后失去指向该分配的最后一个指针。 此规则不应该被理解为要求长期对象中分配必须计划停机期间将返回。 例如,依赖系统保证的清理(例如文件关闭和进程关闭时的内存释放)可以简化代码。 但是,依赖隐式清理的抽象可以简单,通常更安全。

Note

实施终身安全配置可消除泄漏。 当与RAII提供的资源安全相结合时,它消除了“垃圾收集”的需要(通过不产生垃圾)。 将此与类型和边界配置文件的强制执行相结合,您可以获得完整的类型和资源安全性,并由工具保证。

Enforcement
  • 查看指针:将它们分类为非所有者(默认)和所有者。 在可行的情况下,使用标准库资源句柄替换所有者(如上例所示)。 或者,使用GSL的owner标记所有者。
  • 寻找裸newdelete
  • 查找返回原始指针的已知资源分配函数(例如fopenmallocstrdup

P.9:不要浪费时间或空间

Reason

This is C++.

Note

您没有浪费时间和空间来实现目标(例如,开发速度,资源安全性或测试的简化)。 “追求效率的另一个好处是,这个过程迫使你更深入地理解这个问题。” -- Alex Stepanov

Example, bad
struct X {
    char ch;
    int i;
    string s;
    char ch2;

    X& operator=(const X& a);
    X(const X&);
};

X waste(const char* p)
{
    if (!p) throw Nullptr_error{};
    int n = strlen(p);
    auto buf = new char[n];
    if (!buf) throw Allocation_error{};
    for (int i = 0; i < n; ++i) buf[i] = p[i];
    // ... manipulate buffer ...
    X x;
    x.ch = 'a';
    x.s = string(n);    // give x.s space for *p
    for (gsl::index i = 0; i < x.s.size(); ++i) x.s[i] = buf[i];  // copy buf into x.s
    delete[] buf;
    return x;
}

void driver()
{
    X x = waste("Typical argument");
    // ...
}

是的,这是一种讽刺,但我们已经看到在生产代码,更糟的是每一个人的错误。 请注意,X的布局保证浪费至少6个字节(并且很可能更多)。 复制操作的虚假定义会禁用移动语义,因此返回操作很慢(请注意,此处不保证返回值优化,RVO)。 对buf使用newdelete是多余的; 如果我们真的需要一个本地字符串,我们应该使用本地字符串。 其他还有一些性能缺陷和无偿的并发症。

Example, bad
void lower(zstring s)
{
    for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
}

是的,这是从实际项目中代码的例子。 我们留给读者来弄清楚浪费了什么。

Note

浪费(时间或者空间)的个别例子很少有重要意义,如果是重要的,通常很容易被专家消除。 然而,在代码库中广泛传播的浪费很容易变得非常重要,专家并不总是像我们希望的那样可用。 此规则的目标(以及支持它的更具体的规则)是在C ++发生之前消除与使用C ++相关的大多数浪费。 之后,我们可以查看与算法和要求相关的浪费,但这超出了这些指南的范围。

Enforcement

许多更具体的规则旨在实现简化和消除无偿浪费的总体目标。

P.10: 更喜欢不可变数据而不是可变数据

Reason

关于常数而不是关于变量的推理更容易。 不可变的东西不能意外地改变。 有时,不变性可以实现更好的优化。 您不能在常量上进行数据竞争。
See Con: Constants and immutability

P.11:封装凌乱的结构,而不是传播代码

Reason

凌乱的代码更容易隐藏错误,更难写。 良好的界面使用起来更简单,更安全。 凌乱的低级代码会产生更多此类代码。

Example
int sz = 100;
int* p = (int*) malloc(sizeof(int) * sz);
int count = 0;
// ...
for (;;) {
    // ... read an int into x, exit loop if end of file is reached ...
    // ... check that x is valid ...
    if (count == sz)
        p = (int*) realloc(p, sizeof(int) * sz * 2);
    p[count++] = x;
    // ...
}

这是低级的,冗长的,容易出错的。 例如,我们“忘记”测试内存耗尽。 相反,我们可以使用vector:

vector<int> v;
v.reserve(100);
// ...
for (int x; cin >> x; ) {
    // ... check that x is valid ...
    v.push_back(x);
}
Note

标准库和GSL就是这种理念的例子。 例如,我们使用所设计的库,而不是搞乱实现关键抽象(例如vector,span,lock_guard和future)所需的数组,联合,强制转换,棘手的生命周期问题,gsl :: owner等。 由比我们通常拥有的更多时间和专业知识的人实施。 同样,我们可以而且应该设计和实现更专业的库,而不是让用户(通常是我们自己)面临重复获得低级代码的挑战。 这是超集原则子集的变体,是这些准则的基础。

Enforcement
  • 寻找“杂乱的代码”,例如复杂的指针操作和在抽象实现之外的转换。

P.12:适当使用支持工具

Reason

“通过机器”可以做得更好。 计算机不会偷懒或厌倦重复性任务。 我们通常有比做反复做常规任务更好的事情。

Example

运行静态分析器以验证您的代码是否遵循您希望它遵循的准则。

Note Seen
Note

注意不要依赖于过于复杂或过度专业化的工具链。 这些可以使您的便携式代码不可移植。

P.13:根据需要使用支持库

Reason

使用设计良好,文档齐全且支持良好的库可节省时间和精力; 如果您的大部分时间都花在实施上,那么它的质量和文档可能会比您可以做的更大。 图书馆的成本(时间,精力,金钱等)可以在许多用户之间共享。 与单个应用程序相比,广泛使用的库更有可能保持最新并移植到新系统。 了解广泛使用的库可以节省其他/未来项目的时间。 因此,如果您的应用程序域存在合适的库,请使用它。

Example
  std::sort(begin(v), end(v), std::greater<>());

除非您是排序算法的专家并且有足够的时间,否则这更可能是正确的,并且比您为特定应用程序编写的任何内容运行得更快。 您需要一个不使用标准库(或您的应用程序使用的任何基础库)而不是使用它的理由的理由。

Note 默认使用:
Note

如果没有为重要域存在设计良好,文档齐全且支持良好的库,那么您可能应该设计并实现它,然后使用它。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,277评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 那些年我们在一起 那片绿色的操场是我们遇见的校园 那个时候我们在一起 从政法校园到师大的床铺我们在一起 高挑的个子...
    d69c9301a02e阅读 269评论 1 2
  • 从品思T1的课堂上回来,已有15天了;可是一坐到桌前,回想起这三天两夜担任助教的历程,莫名的,眼眶却湿润了。担任助...
    飞翔的涂涂阅读 395评论 0 10
  • 心机深沉的遣词用字 不必弄脏自己的手 也能杀人辱人 ———法国幽默大师皮埃尔.德普罗日 01// 今年一月二十九,...
    旦是但是阅读 1,687评论 2 7