├── .charts ├── github_branch_2.graphml └── github_branch_3.graphml ├── .gitignore ├── .images ├── github_branch_2.png ├── github_branch_3.png └── github_description.png ├── LICENSE ├── README.md ├── md ├── xm_developer_cpp_sytleguide.md ├── xm_developer_github_sytleguide.md ├── xm_developer_python_styleguide.md └── xm_developer_ros_styleguide.md └── pdf ├── xm_developer_cpp_styleguide.pdf ├── xm_developer_github_styleguide.pdf ├── xm_developer_python_styleguide.pdf └── xm_developer_ros_styleguide.pdf /.charts/github_branch_2.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Develop/Stable 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | Reserved b 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | Master 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | Reserved a 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /.charts/github_branch_3.graphml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Stable 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | Reserved b 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | Merge 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | Master 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | Develop a 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | Merge 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | Develop b 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | Reserved c 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | Reserved a 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/.gitignore -------------------------------------------------------------------------------- /.images/github_branch_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/.images/github_branch_2.png -------------------------------------------------------------------------------- /.images/github_branch_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/.images/github_branch_3.png -------------------------------------------------------------------------------- /.images/github_description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/.images/github_description.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | 3 | Copyright (c) 2016, Team-Xmbot-Service-Robot 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | * Neither the name of the Team-Xmbot-Service-Robot nor the names 17 | of its contributors may be used to endorse or promote products 18 | derived from this software without specific prior written 19 | permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xm_developer_guide 2 | 3 | ## Introduction: 4 | 5 | This developer's guide is that our team summarized in the process of developing the xmbot project. It includes C++/Python coding style guide, Github management guide, and ROS developer guide(such as package, CMakelists, msg, srv, aciton, etc). 6 | 7 | - md: Documents are in markdown format. 8 | - pdf: Documents are in pdf format. 9 | 10 | ## Usage: 11 | 12 | Please read these documents carefully!!! 13 | -------------------------------------------------------------------------------- /md/xm_developer_cpp_sytleguide.md: -------------------------------------------------------------------------------- 1 | # Team-Xmbot-Service-Robot C++风格指南 2 | 3 | ## 0. 前言 4 | 5 | ### 0.1 版本 6 | 7 | - 1.0版本(2016.5.1):缪宇飏(myyerrol)创建团队 C++ 代码开发风格指南。本文档参考了 ROS 和 Google 的 C++ 风格指南,并根据实际的需求,对内容进行了适当的精简和改进。新队员应该认真学习本指南,掌握 C++ 基本的开发风格。如果有细节不统一的地方或者对本文档某处不是很认同,请在组内讨论统一之后,修改本指南。因为文档排版使用的是 Markdown 纯文本标记语言,也请后来者遵循本文档的开发方式,使用 Markdown 来修改、添加内容。 8 | 9 | ### 0.2 背景 10 | 11 | C++ 是 ROS 项目开发的主要编程语言。正如每个 C++ 开发者所知道的,C++ 有很多强大的特性,但这种强大不可避免的导致它走向复杂,使代码更容易产生 Bug,难以阅读和维护。 12 | 13 | 因此使代码易于管理的方法之一是加强代码一致性.。让任何其他开发者都可以快速读懂你的代码这点非常重要。保持统一编程风格并遵守约定意味着可以很容易根据“模式匹配”规则来推断各种标识符的含义。创建通用、必需的习惯用语和模式可以使代码更容易理解。在一些情况下可能有充分的理由改变某些编程风格,但我们还是应该遵循一致性原则,尽量不这么做。 14 | 15 | Team-Xmbot-Service-Robot(晓萌家庭服务机器人团队)作为开源项目,需要团队队员贡献代码,但是如果队员之间的代码编程风格不一致,便会给团队其他模块负责人造成不小的困扰。我们认为整洁、一致的代码风格可以使代码更加可读、更加可调试以及更加可维护。因此,我们应该编写优雅的代码使其不仅能够在现在发挥作用,而且在将来的若干年之后其依旧能够存在、可以被复用、或者是能够被未来的新队员改进。 16 | 17 | ## 1. 头文件 18 | 19 | 通常每一个 `.cpp` 文件都有一个对应的 `.h` 文件。也有一些常见例外,如单元测试代码和只包含 `main()` 函数的 `.cpp` 文件。 20 | 正确使用头文件可令代码在可读性、文件大小和性能上大为改观。下面的规则将引导你规避使用头文件时的各种陷阱。 21 | 22 | ### 1.1 \#define 保护 23 | 24 | > **Tip** 25 | > 26 | > 所有头文件都应该使用 `#define` 来防止头文件被多重包含,命名格式当是: `_H` 。 27 | 28 | 为保证唯一性, 头文件的命名应该与头文件的命名一致。例如,`xm_arm_robot_hardware.h` 头文件可按如下方式保护: 29 | 30 | ``` cpp 31 | #ifndef XM_ARM_ROBOT_HARDWARE_H 32 | #define XM_ARM_ROBOT_HARDWARE_H 33 | … 34 | #endif // XM_ARM_ROBOT_HARDWARE_H 35 | ``` 36 | 37 | ### 1.2 `#include` 的路径及顺序 38 | 39 | > **Tip** 40 | > 41 | > 使用标准的头文件包含顺序可增强可读性,避免隐藏依赖:相关头文件、C 库、C++ 库、其他库的 .h,本项目内的 .h。 42 | 43 | `#include` 中包含头文件的次序如下: 44 | 45 | 1. ROS 系统文件 46 | 2. C 系统文件 47 | 3. C++ 系统文件 48 | 4. 其他库的 `.h` 文件 49 | 5. 本项目内 `.h` 文件 50 | 51 | 按字母顺序对头文件包含进行二次排序是不错的主意。注意较老的代码可不符合这条规则,要在方便的时候改正它们。 52 | 53 | 举例来说,头文件包含次序如下: 54 | 55 | ``` cpp 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | ``` 73 | 74 | ## 2. 作用域 75 | 76 | ### 2.1 名字空间 77 | 78 | > **Tip** 79 | > 80 | > 鼓励在 `.cpp` 文件内使用匿名名字空间。使用具名的名字空间时,其名称可基于项目名或相对路径。禁止使用 `using` 指示,禁止使用内联命名空间。 81 | 82 | 定义: 83 | 名字空间将全局作用域细分为独立的,具名的作用域,可有效防止全局作用域的命名冲突。 84 | 85 | 优点: 86 | 虽然类已经提供了(可嵌套的)命名轴线,名字空间在这基础上又封装了一层。举例来说, 两个不同项目的全局作用域都有一个类 `Foo`,这样在编译或运行时造成冲突。如果每个项目将代码置于不同名字空间中,`project1::Foo` 和 `project2::Foo` 作为不同符号自然不会冲突。 87 | 88 | 缺点: 89 | 90 | - 名字空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轴线。 91 | - 命名空间很容易令人迷惑,毕竟它们不再受其声明所在命名空间的限制。内联命名空间只在大型版本控制里有用。 92 | - 在头文件中使用匿名空间导致违背 C++ 的唯一定义原则。 93 | 94 | 结论: 95 | 根据下文将要提到的策略合理使用命名空间。 96 | 97 | #### 2.1.1 匿名名字空间 98 | 99 | - 不要在 `.h` 文件中使用匿名名字空间。 100 | - 在 `.cpp` 文件中,允许甚至鼓励使用匿名名字空间,以避免运行时的命名冲突: 101 | 102 | ``` cpp 103 | namespace { 104 | enum { kUNUSED, kEOF, kERROR }; 105 | bool AtEof() { return pos_ == kEOF; } 106 | ... 107 | } // namespace 108 | ``` 109 | 110 | 然而,与特定类关联的文件作用域声明在该类中被声明为类型,静态数据成员或静态成员函数,而不是匿名名字空间的成员。如上例所示,匿名空间结束时用注释 `// namespace` 标识。 111 | 112 | #### 2.1.2 具名的名字空间 113 | 114 | 具名的名字空间使用方式如下: 115 | 116 | 用名字空间把文件包含,以及类的前置声明以外的整个源文件封装起来,以区别于其它名字空间: 117 | 118 | ``` cpp 119 | namespace mynamespace { 120 | class MyClass 121 | { 122 | public: 123 | void Foo(); 124 | ... 125 | private: 126 | int foo; 127 | ... 128 | }; 129 | } // namespace mynamespace 130 | ``` 131 | 132 | ### 2.2 非成员函数、静态成员函数和全局函数 133 | 134 | > **Tip** 135 | > 136 | > 使用静态成员函数或名字空间内的非成员函数,尽量不要用裸的全局函数。 137 | 138 | 优点: 139 | 某些情况下,非成员函数和静态成员函数是非常有用的,将非成员函数放在名字空间内可避免污染全局作用域。 140 | 141 | 缺点: 142 | 将非成员函数和静态成员函数作为新类的成员或许更有意义,当它们需要访问外部资源或具有重要的依赖关系时更是如此。 143 | 144 | 结论: 145 | 146 | - 有时,把函数的定义同类的实例脱钩是有益的,甚至是必要的。这样的函数可以被定义成静态成员,或是非成员函数。非成员函数不应依赖于外部变量,应尽量置于某个名字空间内。相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用 `namespaces`。 147 | - 定义在同一编译单元的函数,被其他编译单元直接调用可能会引入不必要的耦合和链接时依赖,静态成员函数对此尤其敏感。可以考虑提取到新类中,或者将函数置于独立库的名字空间内。 148 | - 如果你必须定义非成员函数,又只是在 `.cpp` 文件中使用它,可使用匿名namespaces或 `static` 链接关键字(如 `static int Foo() {...}`)限定其作用域。 149 | 150 | ### 2.3 局部变量 151 | 152 | > **Tip** 153 | > 154 | > 将函数变量尽可能置于最小作用域内,并在变量声明时进行初始化。 155 | 156 | C++ 允许在函数的任何位置声明变量。我们提倡在尽可能小的作用域中声明变量,离第一次使用越近越好。这使得代码浏览者更容易定位变量声明的位置,了解变量的类型和初始值。特别是,应使用初始化的方式替代声明再赋值,比如: 157 | 158 | ```cpp 159 | int i; 160 | // 警告:初始化和声明分离。 161 | i = f(); 162 | // 正确:初始化时声明。 163 | int j = g(); 164 | 165 | vector v; 166 | // 优化:用花括号初始化更好。 167 | v.push_back(1); 168 | v.push_back(2); 169 | 170 | // 正确:v 一开始就初始化。 171 | vector v = {1, 2}; 172 | ``` 173 | 174 | 注意,GCC 可正确实现了 `for (int i = 0; i < 10; ++i)`(`i` 的作用域仅限 `for` 循环内),所以其他 `for` 循环中可以重新使用 `i` 。在 `if` 和 `while` 等语句中的作用域声明也是正确的,如: 175 | 176 | ```cpp 177 | while (const char* p = strchr(str, ‘/’)) str = p + 1; 178 | ``` 179 | 180 | > **Warning** 181 | > 182 | > 如果变量是一个对象,每次进入作用域都要调用其构造函数,每次退出作用域都要调用其析构函数。 183 | 184 | ```cpp 185 | // 低效的实现。 186 | for (int i = 0; i < 1000000; i++) 187 | { 188 | // 构造函数和析构函数分别调用 1000000 次! 189 | Foo f; 190 | f.DoSomething(i); 191 | } 192 | ``` 193 | ```cpp 194 | // 高效的实现。 195 | // 构造函数和析构函数只调用 1 次。 196 | Foo f; 197 | for (int i = 0; i < 1000000; i++) 198 | { 199 | f.DoSomething(i); 200 | } 201 | ``` 202 | 203 | ## 3. 类 204 | 205 | 类是 C++ 中代码的基本单元。显然,它们被广泛使用。本节列举了在写一个类时的主要注意事项。 206 | 207 | ### 3.1 构造函数的职责 208 | 209 | > **Tip** 210 | > 211 | > 不要在构造函数中进行复杂的初始化(尤其是那些有可能失败或者需要调用虚函数的初始化)。 212 | 213 | 定义: 214 | 在构造函数体中进行初始化操作。 215 | 216 | 优点: 217 | 排版方便,无需担心类是否已经初始化。 218 | 219 | 缺点: 220 | 在构造函数中执行操作引起的问题有: 221 | 222 | - 构造函数中很难上报错误,不能使用异常。 223 | - 操作失败会造成对象初始化失败,进入不确定状态。 224 | - 如果在构造函数内调用了自身的虚函数,这类调用是不会重定向到子类的虚函数实现。即使当前没有子类化实现,将来仍是隐患。 225 | - 如果有人创建该类型的全局变量(虽然违背了上节提到的规则),构造函数将先 `main()` 一步被调用,有可能破坏构造函数中暗含的假设条件。 226 | 227 | 结论: 228 | 构造函数不得调用虚函数,或尝试报告一个非致命错误。如果对象需要进行有意义的初始化,考虑使用明确的 `Init()` 方法或使用工厂模式。 229 | 230 | ### 3.2 初始化 231 | 232 | > **Tip** 233 | > 234 | > 如果类中定义了成员变量,则必须在类中为每个类提供初始化函数或定义一个构造函数。若未声明构造函数,则编译器会生成一个默认的构造函数,这有可能导致某些成员未被初始化或被初始化为不恰当的值。 235 | 236 | 定义: 237 | `new` 一个不带参数的类对象时,会调用这个类的默认构造函数。用 `new[]` 创建数组时,默认构造函数则总是被调用。在类成员里面进行初始化是指声明一个成员变量的时候使用一个结构例如 `int _count = 17` 或者 `string _name{"abc"}` 来替代 `int _count` 或者 `string _name` 这样的形式。 238 | 239 | 优点: 240 | 用户定义的默认构造函数将在没有提供初始化操作时将对象初始化。这样就保证了对象在被构造之时就处于一个有效且可用的状态,同时保证了对象在被创建时就处于一个显然”不可能“的状态,以此帮助调试。 241 | 242 | 缺点: 243 | 244 | - 对代码编写者来说, 这是多余的工作。 245 | - 如果一个成员变量在声明时初始化又在构造函数中初始化,有可能造成混乱,因为构造函数中的值会覆盖掉声明中的值。 246 | 247 | 结论: 248 | 249 | - 简单的初始化用类成员初始化完成,尤其是当一个成员变量要在多个构造函数里用相同的方式初始化的时候。 250 | - 如果你的类中有成员变量没有在类里面进行初始化,而且没有提供其它构造函数,你必须定义一个(不带参数的)默认构造函数。把对象的内部状态初始化成一致/有效的值无疑是更合理的方式。这么做的原因是:如果你没有提供其它构造函数,又没有定义默认构造函数,编译器将为你自动生成一个。编译器生成的构造函数并不会对对象进行合理的初始化。 251 | - 如果你定义的类继承现有类,而你又没有增加新的成员变量,则不需要为新类定义默认构造函数。 252 | 253 | ### 3.3 结构体 VS 类 254 | 255 | > **Tip** 256 | > 257 | > 仅当只有数据时使用 `struct`,其它一概使用 `class`。 258 | 259 | 结论: 260 | 261 | - 在 C++ 中, `struct` 和 `class` 关键字几乎含义一样。我们为这两个关键字添加我们自己的语义理解,以便为定义的数据类型选择合适的关键字。 262 | - `struct` 用来定义包含数据的被动式对象,也可以包含相关的常量,但除了存取数据成员之外,没有别的函数功能。并且存取功能是通过直接访问位域,而非函数调用。除了构造函数、析构函数、`Initialize()`、`Reset()`、`Validate()` 等类似的函数外,不能提供其它功能的函数。 263 | - 如果需要更多的函数功能,`class` 更适合。如果拿不准,就用 `class`。 264 | - 注意,类和结构体的成员变量使用不同的命名规则。 265 | 266 | ### 3.4 继承 267 | 268 | > **Tip** 269 | > 270 | > 使用组合常常比使用继承更合理。如果使用继承的话,定义为 `public` 继承。 271 | 272 | 定义: 273 | 当子类继承基类时,子类包含了父基类所有数据及操作的定义。C++ 实践中,继承主要用于两种场合:实现继承,即子类继承父类的实现代码。接口继承,即子类仅继承父类的方法名称。 274 | 275 | 优点: 276 | 实现继承通过原封不动的复用基类代码减少了代码量。由于继承是在编译时声明,程序员和编译器都可以理解相应操作并发现错误。从编程角度而言,接口继承是用来强制类输出特定的 API。在类没有实现 API 中某个必须的方法时,编译器同样会发现并报告错误。 277 | 278 | 缺点: 279 | 对于实现继承,由于子类的实现代码散布在父类和子类间之间,要理解其实现变得更加困难。子类不能重写父类的非虚函数,当然也就不能修改其实现。基类也可能定义了一些数据成员,还要区分基类的实际布局。 280 | 281 | 结论: 282 | 283 | - 所有继承必须是 `public` 的。如果你想使用私有继承,你应该替换成把基类的实例作为成员对象的方式。 284 | - 不要过度使用实现继承。组合常常更合适一些。尽量做到只在 “是一个”的情况下使用继承:如果 `Bar` 的确 “是一种“ Foo,`Bar` 才能继承 `Foo`。 285 | - 必要的话,析构函数声明为 `virtual`。如果你的类有虚函数,则析构函数也应该为虚函数。注意**数据成员在任何情况下都必须是私有的**。 286 | - 当重载一个虚函数,在衍生类中把它明确的声明为 `virtual`。理论依据:如果省略 `virtual` 关键字,代码阅读者不得不检查所有父类,以判断该函数是否是虚函数。 287 | 288 | ### 3.5 声明顺序 289 | 290 | > **Tip** 291 | > 292 | > 在类中使用特定的声明顺序:`public:` 在 `private:` 之前,成员函数在数据成员(变量)前。 293 | 294 | 类的访问控制区段的声明顺序依次为:`public:`、`protected:`、`private:`。如果某区段没内容,可以不声明。 295 | 296 | 每个区段内的声明通常按以下顺序: 297 | 298 | 1. `typedefs` 和枚举 299 | 2. 常量 300 | 3. 构造函数 301 | 4. 析构函数 302 | 5. 成员函数,含静态成员函数 303 | 6. 数据成员,含静态数据成员 304 | 305 | `.cpp` 文件中函数的定义应尽可能和声明顺序一致。 306 | 307 | ### 3.6 编写简短函数 308 | 309 | > **Tip** 310 | > 311 | > 倾向编写简短,凝练的函数。 312 | 313 | 我们承认长函数有时是合理的,因此并不硬性限制函数的长度。如果函数超过 40 行,可以思索一下能不能在不影响程序结构的前提下对其进行分割。 314 | 315 | 即使一个长函数现在工作的非常好,一旦有人对其修改,有可能出现新的问题。甚至导致难以发现的 Bug。使函数尽量简短,便于他人阅读和修改代码。 316 | 317 | 在处理代码时,你可能会发现复杂的长函数。不要害怕修改现有代码:如果证实这些代码使用/调试困难,或者你需要使用其中的一小段代码,考虑将其分割为更加简短并易于管理的若干函数。 318 | 319 | ## 4. 其他 C++ 特性 320 | 321 | ### 4.1 引用参数 322 | 323 | > **Tip** 324 | > 325 | > 所有按引用传递的参数必须加上 `const`。 326 | 327 | 定义: 328 | 在 C 语言中,如果函数需要修改变量的值,参数必须为指针,如 `int foo(int *pval)`。在 C++ 中,函数还可以声明引用参数:`int foo(int &val)`。 329 | 330 | 优点: 331 | 定义引用参数防止出现 `(*pval)++` 这样丑陋的代码。像拷贝构造函数这样的应用也是必需的。而且更明确,不接受 `NULL` 指针。 332 | 333 | 缺点: 334 | 容易引起误解,因为引用在语法上是值变量却拥有指针的语义。 335 | 336 | 结论: 337 | 函数参数列表中,所有引用参数都必须是 `const`: 338 | 339 | ``` cpp 340 | void Foo(const string &in, string *out); 341 | ``` 342 | 343 | 事实上这是一个硬性约定:输入参数是值参或 `const` 引用,输出参数为指针。输入参数可以是 `const` 指针,但决不能是非 `const` 的引用参数,除非用于交换,比如 `swap()`。 344 | 345 | 有时候,在输入形参中用 `const T*` 指针比 `const T&` 更明智。比如: 346 | 347 | - 您会传 `null` 指针。 348 | - 函数要把指针或对地址的引用赋值给输入形参。 349 | 350 | 总之大多时候输入形参往往是 `const T&`。若用 `const T*` 说明输入另有处理。所以若您要用 `const T*`,则应有理有据,否则会害得读者误解。 351 | 352 | ### 4.2 函数重载 353 | 354 | > **Tip** 355 | > 356 | > 若要用好函数重载,最好能让读者一看调用点就胸有成竹,不用花心思猜测调用的重载函数到底是哪一种。该规则适用于构造函数。 357 | 358 | 定义: 359 | 你可以编写一个参数类型为 `const string&` 的函数,然后用另一个参数类型为 `const char*` 的函数重载它: 360 | 361 | ``` cpp 362 | class MyClass 363 | { 364 | public: 365 | void Analyze(const string &text); 366 | void Analyze(const char *text, size_t textlen); 367 | }; 368 | ``` 369 | 370 | 优点: 371 | 通过重载参数不同的同名函数,令代码更加直观。模板化代码需要重载,同时为使用者带来便利。 372 | 373 | 缺点: 374 | 如果函数单单靠不同的参数类型而重载,读者就得十分熟悉 C++ 五花八门的匹配规则,以了解匹配过程具体到底如何。另外,当派生类只重载了某个函数的部分变体,继承语义容易令人困惑。 375 | 376 | 结论: 377 | 如果您打算重载一个函数,可以试试改在函数名里加上参数信息。例如,用 `AppendString()` 和 `AppendInt()` 等, 而不是一口气重载多个 `Append()`。 378 | 379 | ### 4.3 缺省参数 380 | 381 | > **Tip** 382 | > 383 | > 我们不允许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载。 384 | 385 | 优点: 386 | 当您有依赖缺省参数的函数时,您也许偶尔会修改修改这些缺省参数。通过缺省参数,不用再为个别情况而特意定义一大堆函数了。与函数重载相比,缺省参数语法更为清晰,代码少,也很好地区分了「必选参数」和「可选参数」。 387 | 388 | 缺点: 389 | 缺省参数会干扰函数指针,害得后者的函数签名往往对不上所实际要调用的函数签名。即在一个现有函数添加缺省参数,就会改变它的类型,那么调用其地址的代码可能会出错,不过函数重载就没这问题了。此外,缺省参数会造成臃肿的代码,毕竟它们在每一个调用点都有重复。函数重载正好相反,毕竟它们所谓的「缺省参数」只会出现在函数定义里。 390 | 391 | 结论: 392 | 由于缺点并不是很严重,有些人依旧偏爱缺省参数胜于函数重载。所以除了以下情况,我们要求必须显式提供所有参数: 393 | 394 | - 位于 `.cpp` 文件里的静态函数或匿名空间函数,毕竟都只能在局部文件里调用该函数了。 395 | - 可以在构造函数里用缺省参数,毕竟不可能取得它们的地址。 396 | - 可以用来模拟变长数组。 397 | 398 | ``` cpp 399 | // 通过空 AlphaNum 以支持四个形参。 400 | string StrCat(const AlphaNum &a, 401 | const AlphaNum &b = gEmptyAlphaNum, 402 | const AlphaNum &c = gEmptyAlphaNum, 403 | const AlphaNum &d = gEmptyAlphaNum); 404 | ``` 405 | 406 | ### 4.4 友元 407 | 408 | > **Tip** 409 | > 410 | > 我们允许合理的使用友元类及友元函数。 411 | 412 | 通常友元应该定义在同一文件内,避免代码读者跑到其它文件查找使用该私有成员的类。经常用到友元的一个地方是将 `FooBuilder` 声明为 `Foo` 的友元,以便 `FooBuilder` 正确构造 `Foo` 的内部状态,而无需将该状态暴露出来。某些情况下,将一个单元测试类声明成待测类的友元会很方便。 413 | 414 | 友元扩大了(但没有打破)类的封装边界。某些情况下,相对于将类成员声明为 `public`,使用友元是更好的选择,尤其是如果你只允许另一个类访问该类的私有成员时。当然,大多数类都只应该通过其提供的公有成员进行互操作。 415 | 416 | ### 4.5 异常 417 | 418 | > **Tip** 419 | > 420 | > 我们不使用 C++ 异常。 421 | 422 | 优点: 423 | 424 | - 异常允许应用高层决定如何处理在底层嵌套函数中「不可能发生」的失败,不用管那些含糊且容易出错的错误代码。 425 | - 很多现代语言都用异常。引入异常使得 C+ +与 Python、Java 以及其它类 C++ 的语言更一脉相承。 426 | - 有些第三方 C++ 库依赖异常,禁用异常就不好用了。 427 | - 异常是处理构造函数失败的唯一途径。虽然可以用工厂函数或 `Init()` 方法代替异常。 428 | - 在测试框架里很好用。 429 | 430 | 缺点: 431 | 432 | - 在现有函数中添加 `throw` 语句时,您必须检查所有调用点。要么让所有调用点统统具备最低限度的异常安全保证,要么眼睁睁地看异常一路欢快地往上跑,最终中断掉整个程序。举例,`f()` 调用 `g()`,`g()` 又调用 `h()`,且 `h` 抛出的异常被 `f` 捕获。当心 `g`,否则会没妥善清理好。 433 | - 还有更常见的,异常会彻底扰乱程序的执行流程并难以判断,函数也许会在您意料不到的地方返回。您或许会加一大堆何时何处处理异常的规定来降低风险,然而开发者的记忆负担更重了。 434 | - 启用异常会增加二进制文件数据,延长编译时间(或许影响小),还可能加大地址空间的压力。 435 | - 滥用异常会变相鼓励开发者去捕捉不合时宜,或本来就已经没法恢复的「伪异常」。比如,用户的输入不符合格式要求时,也用不着抛异常。如此之类的伪异常列都列不完。 436 | 437 | 结论: 438 | 439 | - 从表面上看来,使用异常利大于弊,尤其是在新项目中。但是对于现有代码,引入异常会牵连到所有相关代码。如果新项目允许异常向外扩散,在跟以前未使用异常的代码整合时也将是个麻烦。因为现有的大多数 C++ 代码都没有异常处理,引入带有异常处理的新代码相当困难。 440 | - 鉴于现有代码不接受异常,在现有代码中使用异常比在新项目中使用的代价多少要大一些。迁移过程比较慢,也容易出错。我们不相信异常的使用有效替代方案,如错误代码、断言等会造成严重负担。因此我们不建议使用异常。 441 | 442 | ### 4.6 类型转换 443 | 444 | > **Tip** 445 | > 446 | > 使用 C++ 的类型转换,如 `static_cast<>()`。不要使用 `int y = (int)x` 或 `int y = int(x)` 等转换方式。 447 | 448 | 定义: 449 | C++ 采用了有别于 C 的类型转换机制,对转换操作进行归类。 450 | 451 | 优点: 452 | C 语言的类型转换问题在于模棱两可的操作。有时是在做强制转换(如 `(int)3.5`),有时是在做类型转换(如 `(int)"hello"`)。另外,C++ 的类型转换在查找时更醒目。 453 | 454 | 缺点: 455 | 恶心的语法。 456 | 457 | 结论: 458 | 不要使用 C 风格类型转换。而应该使用 C++ 风格: 459 | 460 | - 用 `static_cast` 替代 C 风格的值转换,或某个类指针需要明确的向上转换为父类指针的时候。 461 | - 用 `const_cast` 去掉 `const` 限定符。 462 | - 用 `reinterpret_cast` 指针类型和整型或其它指针之间进行不安全的相互转换,仅在你对所做一切了然于心时使用。 463 | 464 | ### 4.7 流 465 | 466 | > **Tip** 467 | > 468 | > 只在记录日志时使用流。 469 | 470 | 定义: 471 | 流用来替代 `printf()` 和 `scanf()`。 472 | 473 | 优点: 474 | 有了流,在打印时不需要关心对象的类型。不用担心格式化字符串与参数列表不匹配(虽然在 GCC 中使用 `printf` 也不存在这个问题)。流的构造和析构函数会自动打开和关闭对应的文件。 475 | 476 | 缺点: 477 | 流使得 `pread()` 等功能函数很难执行。如果不使用 `printf` 风格的格式化字符串,某些格式化操作(尤其是常用的格式字符串 `%.*s`)用流处理性能是很低的。流不支持字符串操作符重新排序(%1s),而这一点对于软件国际化很有用。 478 | 479 | 结论: 480 | 481 | - 不要使用流,除非是日志接口需要。使用 `printf` 之类的代替。 482 | - 使用流还有很多利弊,但代码一致性胜过一切。不要在代码中使用流。 483 | 484 | 拓展讨论: 485 | 486 | - 对这一条规则存在一些争论,这儿给出点深层次原因。回想一下唯一性原则:我们希望在任何时候都只使用一种确定的 I/O 类型,使代码在所有 I/O 处都保持一致。因此,我们不希望用户来决定是使用流还是 `printf + read/write`。相反,我们应该决定到底用哪一种方式。把日志作为特例是因为日志是一个非常独特的应用,还有一些是历史原因。 487 | - 流的支持者们主张流是不二之选,但观点并不是那么清晰有力。他们指出的流的每个优势也都是其劣势。流最大的优势是在输出时不需要关心打印对象的类型。这是一个亮点。同时,也是一个不足:你很容易用错类型,而编译器不会报警。使用流时容易造成的这类错误: 488 | 489 | ``` cpp 490 | // 输出地址。 491 | cout << this; 492 | // 输出值。 493 | cout << *this; 494 | ``` 495 | 496 | 由于 `<<` 被重载,编译器不会报错。就因为这一点我们反对使用操作符重载。 497 | 498 | 有人说 `printf` 的格式化丑陋不堪,易读性差,但流也好不到哪儿去。看看下面两段代码吧,实现相同的功能,哪个更清晰? 499 | 500 | ``` cpp 501 | cerr << "Error connecting to '" << foo->bar()->hostname.first 502 | << ":" << foo->bar()->hostname.second << ":" << strerror(errno); 503 | 504 | fprintf(stderr, "Error connecting to '%s:%u:%s", 505 | foo->bar()->hostname.first, foo->bar()->hostname.second, 506 | strerror(errno)); 507 | ``` 508 | 509 | 你可能会说,”把流封装一下就会比较好了”,这儿可以,其他地方呢?而且不要忘了,我们的目标是使语言更紧凑,而不是添加一些别人需要学习的新装备。 510 | 511 | 每一种方式都是各有利弊,“没有最好,只有更适合”。简单性原则告诫我们必须从中选择其一,最后大多数决定采用 `printf + read/write`。 512 | 513 | ### 4.8 前置自增和自减 514 | 515 | > **Tip** 516 | > 517 | > 对于迭代器和其他模板对象使用前缀形式(`++i`)的自增,自减运算符。 518 | 519 | 定义: 520 | 对于变量在自增 (`++i` 或 `i++`)或自减(`--i` 或 `i--`)后表达式的值又没有没用到的情况下,需要确定到底是使用前置还是后置的自增(自减)。 521 | 522 | 优点: 523 | 不考虑返回值的话,前置自增(`++i`)通常要比后置自增(`i++`)效率更高。因为后置自增(或自减)需要对表达式的值 `i` 进行一次拷贝。如果 `i` 是迭代器或其他非数值类型,拷贝的代价是比较大的。既然两种自增方式实现的功能一样,为什么不总是使用前置自增呢? 524 | 525 | 缺点: 526 | 在 C 开发中,当表达式的值未被使用时,传统的做法是使用后置自增,特别是在 `for` 循环中。有些人觉得后置自增更加易懂,因为这很像自然语言,主语(`i`)在谓语动词(`++`)前。 527 | 528 | 结论: 529 | 对简单数值 (非对象),两种都无所谓。对迭代器和模板类型,使用前置自增(自减)。 530 | 531 | ### 4.9 `const` 用法 532 | 533 | > **Tip** 534 | > 535 | > 我们强烈建议你在任何可能的情况下都要使用 `const`。 536 | 537 | 定义: 538 | 在声明的变量或参数前加上关键字 `const` 用于指明变量值不可被篡改(如 `const int foo`)。为类中的函数加上 `const` 限定符表明该函数不会修改类成员变量的状态(如 `class Foo { int Bar(char c) const; };`)。 539 | 540 | 优点: 541 | 大家更容易理解如何使用变量。编译器可以更好地进行类型检测,相应地,也能生成更好的代码。人们对编写正确的代码更加自信,因为他们知道所调用的函数被限定了能或不能修改变量值。即使是在无锁的多线程编程中,人们也知道什么样的函数是安全的。 542 | 543 | 缺点: 544 | `const` 是入侵性的:如果你向一个函数传入 `const` 变量,函数原型声明中也必须对应 `const` 参数(否则变量需要 `const_cast` 类型转换),在调用库函数时显得尤其麻烦。 545 | 546 | 结论: 547 | `const` 变量、数据成员、函数和参数为编译时类型检测增加了一层保障,便于尽早发现错误。因此,我们强烈建议在任何可能的情况下使用 `const`: 548 | 549 | - 如果函数不会修改传你入的引用或指针类型参数,该参数应声明为 `const`。 550 | - 尽可能将函数声明为 `const`。访问函数应该总是 `const`。其他不会修改任何数据成员,未调用非 `const` 函数,不会返回数据成员非 `const` 指针或引用的函数也应该声明成 `const`。 551 | - 如果数据成员在对象构造之后不再发生变化,可将其定义为 `const`。 552 | 553 | 然而,也不要发了疯似的使用 `const`。像 `const int * const * const x;` 就有些过了,虽然它非常精确的描述了常量 `x`。关注真正有帮助意义的信息:前面的例子写成 `const int** x` 就够了。 554 | 555 | `const` 的位置: 556 | 557 | 有人喜欢 `int const *foo` 形式,不喜欢 `const int* foo`,他们认为前者更一致因此可读性也更好:遵循了 `const` 总位于其描述的对象之后的原则。但是一致性原则不适用于此,“不要过度使用“ 的声明可以取消大部分你原本想保持的一致性。将 `const` 放在前面才更易读,因为在自然语言中形容词(`const`)是在名词(`int`)之前。 558 | 559 | 这是说,我们提倡但不强制 `const` 在前。但要保持代码的一致性! 560 | 561 | ### 4.10 整型 562 | 563 | > **Tip** 564 | > 565 | > C++ 内建整型中,仅使用 `int`。如果程序中需要不同大小的变量,可以使用 `` 中长度精确的整型,如 `int16_t`。如果您的变量可能不小于 2^31(2GiB),就用 64 位变量比如 `int64_t`. 此外要留意,哪怕您的值并不会超出 `int` 所能够表示的范围,在计算过程中也可能会溢出。所以拿不准时,干脆用更大的类型。 566 | 567 | 定义: 568 | C++ 没有指定整型的大小。通常人们假定 `short` 是 16 位,`int` 是 32 位,`long` 是 32 位,`long long` 是 64 位。 569 | 570 | 优点: 571 | 保持声明统一。 572 | 573 | 缺点: 574 | C++ 中整型大小因编译器和体系结构的不同而不同。 575 | 576 | 结论: 577 | 578 | - `` 定义了 `int16_t`、`uint32_t`、`int64_t` 等整型,在需要确保整型大小时可以使用它们代替 `short`、`unsigned long long` 等。在 C 整型中,只使用 `int`。在合适的情况下,推荐使用标准类型如 `size_t` 和 `ptrdiff_t`。 579 | - 如果已知整数不会太大,我们常常会使用 `int`,如循环计数。在类似的情况下使用原生类型 `int`。你可以认为 `int` 至少为 32 位,但不要认为它会多于 `32` 位。如果需要 64 位整型,用 `int64_t` 或 `uint64_t`。 580 | - 对于大整数,使用 `int64_t`。 581 | - 不要使用 `uint32_t` 等无符号整型,除非你是在表示一个位组而不是一个数值,或是你需要定义二进制补码溢出。尤其是不要为了指出数值永不会为负,而使用无符号类型。相反,你应该使用断言来保护数据。 582 | - 如果您的代码涉及容器返回的大小,确保其类型足以应付容器各种可能的用法。拿不准时,类型越大越好。 583 | - 小心整型类型转换和整型提升,总有意想不到的后果。 584 | 585 | 关于无符号整数: 586 | 587 | 有些人,包括一些教科书作者,推荐使用无符号类型表示非负数。这种做法试图达到自我文档化。但是,在 C 语言中,这一优点被由其导致的 Bug 所淹没。看看下面的例子: 588 | 589 | ``` cpp 590 | for (unsigned int i = foo.Length() - 1; i >= 0; --i) ... 591 | ``` 592 | 593 | 上述循环永远不会退出!有时 GCC 会发现该 Bug 并报警,但大部分情况下都不会。类似的 Bug 还会出现在比较有符合变量和无符号变量时。主要是 C 的类型提升机制会致使无符号类型的行为出乎你的意料。 594 | 595 | 因此,使用断言来指出变量为非负数,而不是使用无符号型! 596 | 597 | ### 4.11 `nullptr` 和 `NULL` 598 | 599 | > **Tip** 600 | > 601 | > 整数用 `0`,实数用 `0.0`,指针用 `nullptr` 或 `NULL`,字符 (串) 用 `'\0'`。 602 | 603 | ### 4.12 `sizeof` 604 | 605 | > **Tip** 606 | > 607 | > 尽可能用 `sizeof(varname)` 代替 `sizeof(type)`。 608 | > 609 | > 使用 `sizeof(varname)` 是因为当代码中变量类型改变时会自动更新。您或许会用 `sizeof(type)` 处理不涉及任何变量的代码,比如处理来自外部或内部的数据格式,这时用变量就不合适了。 610 | 611 | ``` cpp 612 | Struct data; 613 | memset(&data, 0, sizeof(data)); 614 | ``` 615 | 616 | ### 4.13 Boost 库 617 | 618 | > **Tip** 619 | > 620 | > 只使用 Boost 中被认可的库。 621 | 622 | 定义: 623 | [Boost 库集](http://www.boost.org) 是一个广受欢迎,经过同行鉴定,免费开源的 C++ 库集。 624 | 625 | 优点: 626 | Boost代码质量普遍较高,可移植性好,填补了 C++ 标准库很多空白,如型别的特性,更完善的绑定器,更好的智能指针。 627 | 628 | 缺点: 629 | 某些 Boost 库提倡的编程实践可读性差,比如元编程和其他高级模板技术,以及过度”函数化”的编程风格。 630 | 631 | 结论: 632 | 为了向阅读和维护代码的人员提供更好的可读性,我们只允许使用 Boost 一部分经认可的特性子集。 633 | 634 | ## 5. 命名约定 635 | 636 | 最重要的一致性规则是命名管理。命名风格快速获知名字代表是什么东东:类型?变量?函数?常量?宏 ...?甚至不需要去查找类型声明。我们大脑中的模式匹配引擎可以非常可靠的处理这些命名规则。 637 | 638 | 命名规则具有一定随意性, 但相比按个人喜好命名, 一致性更重, 所以不管你怎么想, 规则总归是规则。 639 | 640 | ### 5.1 通用命名规则 641 | 642 | > **Tip** 643 | > 644 | > 函数命名,变量命名,文件命名要有描述性,少用缩写。 645 | > 646 | > 尽可能给有描述性的命名,别心疼空间,毕竟让代码易于新读者理解很重要。不要用只有项目开发者能理解的缩写,也不要通过砍掉几个字母来缩写单词。 647 | 648 | ``` cpp 649 | int price_count_reader; 650 | int num_errors; 651 | int num_dns_connections; 652 | ``` 653 | 654 | ### 5.2 文件命名 655 | 656 | > **Tip** 657 | > 658 | > 文件名要全部小写, 可以包含下划线(`_`)。 659 | 660 | 可接受的文件命名: 661 | 662 | - xm_arm_robot_hardware.cpp 663 | - xm_arm_gripper_control.cpp 664 | - xm_arm_trajectory_control.cpp 665 | 666 | C++ 文件要以 `.cpp` 结尾, 头文件以 `.h` 结尾。 667 | 668 | 通常应尽量让文件名更加明确. `xm_arm_trajectory_control.cpp` 就比 `xm_arm_control.cpp` 要好。`.h` 和 `.cpp` 要成对出现, 如 `xm_arm_robot_hardware.h` 和 `xm_arm_robot_hardware.cpp`。 669 | 670 | ### 5.3 类型命名 671 | 672 | > **Tip** 673 | > 674 | > 类型名称的每个单词首字母均大写,不包含下划线:`MyExcitingClass`,`MyExcitingEnum`。 675 | 676 | 所有类型命名 —— 类、结构体、类型定义(`typedef`)、枚举 —— 均使用相同约定,而且名字应该是名词性的。 677 | 678 | ### 5.4 变量命名 679 | 680 | > **Tip** 681 | > 682 | > 变量名一律小写,单词之间用下划线连接。类的成员变量以下划线结尾,但结构体的就不用,如: `a_local_variable`、`a_struct_data_member`、`a_class_data_member_`。 683 | 684 | 普通变量命名: 685 | 686 | ``` cpp 687 | string table_name; 688 | string tablename; 689 | ``` 690 | 691 | 类数据成员: 692 | 693 | 不管是静态的还是非静态的,类数据成员都可以和普通变量一样,但要接下划线。 694 | 695 | ``` cpp 696 | class TableInfo 697 | { 698 | ... 699 | private: 700 | string table_name_; 701 | string tablename_; 702 | static Pool *pool_; 703 | }; 704 | ``` 705 | 706 | 结构体变量: 707 | 708 | 不管是静态的还是非静态的,结构体数据成员都可以和普通变量一样,不用像类那样接下划线: 709 | 710 | ``` cpp 711 | struct UrlTableProperties 712 | { 713 | string name; 714 | int num_entries; 715 | } 716 | ``` 717 | 718 | 全局变量: 719 | 720 | 对全局变量没有特别要求,少用就好,但如果你要用,可以用 `g_` 标志作为前缀,以便更好的区分局部变量。 721 | 722 | ### 5.5 常量命名 723 | 724 | > **Tip** 725 | > 726 | > 在全局或类里的常量名称前加 `k`:kDaysInAWeek。且除去开头的 `k` 之外每个单词开头字母均大写。 727 | > 728 | > 所有编译时常量,无论是局部的,全局的还是类中的,和其他变量稍微区别一下。`k` 后接大写字母开头的单词: 729 | 730 | ``` cpp 731 | const int kDaysInAWeek = 7; 732 | ``` 733 | 734 | 这规则适用于编译时的局部作用域常量,不过要按变量规则来命名也可以。 735 | 736 | ### 5.6 函数命名 737 | 738 | > **Tip** 739 | > 740 | > 函数使用驼峰命名法(camelCased): `getExcitingResult()`, `checkForErrors()` 741 | 742 | 函数或方法通常执行的是一个动作。因此,它们的名字应该能够反映出它们能做什么(动词性的)。使用`checkForErrors()`来代替`errorCheck()`,`dumpDataToFile() `代替`dataFile()`。 743 | 744 | ### 5.7 名字空间命名 745 | 746 | > **Tip** 747 | > 748 | > 名字空间用小写字母命名:`xm_arm_namespace`。 749 | 750 | ### 5.8 枚举命名 751 | 752 | > **Tip** 753 | > 754 | > 枚举的命名应当和宏一致:`ENUM_NAME`。 755 | 756 | 枚举值应该采用宏的命名方式。 757 | ``` cpp 758 | enum AlternateUrlTableErrors 759 | { 760 | OK = 0, 761 | OUT_OF_MEMORY = 1, 762 | MALFORMED_INPUT = 2 763 | }; 764 | ``` 765 | 766 | ### 5.9 宏命名 767 | 768 | > **Tip** 769 | > 770 | > 名字全部大写并且可加下划线,比如:`PI_ROUNDED`。 771 | 772 | ## 6. 注释 773 | 774 | > **Tip** 775 | > 776 | > 在程序难以理解的地方加上注释,而且全部要用英文写,不要用中文。 777 | 778 | 注释虽然写起来很痛苦,但对保证代码可读性至关重要。下面的规则描述了如何注释以及在哪儿注释。当然也要记住:注释固然很重要,但最好的代码本身应该是自文档化。有意义的类型名和变量名,要远胜过要用注释解释的含糊不清的名字。 779 | 780 | 你写的注释是给代码读者看的:对下一个需要理解你的代码的人慷慨些吧, 因为下一个人可能就是你! 781 | 782 | ### 6.1 注释风格 783 | 784 | > **Tip** 785 | > 786 | > 使用 `//` 或 `/* */`,统一就好。 787 | 788 | `//` 或 `/* */` 都可以。但 `//` 更常用。要在如何注释及注释风格上确保统一。 789 | 790 | ### 6.2 文件注释 791 | 792 | > **Tip** 793 | > 794 | > 在每一个文件开头加入版权公告,然后是文件内容描述。 795 | 796 | 法律公告和作者信息: 797 | 798 | 每个文件都应该包含以下项, 依次是: 799 | 800 | - 版权声明(比如,`Copyright (c) 2016, Team-Xmbot-Service-Robot`) 801 | - 许可证:为项目选择合适的许可证版本(BSD) 802 | - 作者:标识文件的原始作者。 803 | - 时间:创建文件的时间。 804 | 805 | 如果你对原始作者的文件做了重大修改,将你的信息添加到作者信息里。这样当其他人对该文件有疑问时可以知道该联系谁。 806 | 807 | 举个例子: 808 | 809 | ```cpp 810 | /********************************************************************* 811 | * Software License Agreement (BSD License) 812 | * 813 | * Copyright (c) 2016, Team-Xmbot-Service-Robot 814 | * All rights reserved. 815 | * 816 | * Redistribution and use in source and binary forms, with or without 817 | * modification, are permitted provided that the following conditions 818 | * are met: 819 | * 820 | * * Redistributions of source code must retain the above copyright 821 | * notice, this list of conditions and the following disclaimer. 822 | * * Redistributions in binary form must reproduce the above 823 | * copyright notice, this list of conditions and the following 824 | * disclaimer in the documentation and/or other materials provided 825 | * with the distribution. 826 | * * Neither the name of the Team-Xmbot-Service-Robot nor the names 827 | * of its contributors may be used to endorse or promote products 828 | * derived from this software without specific prior written 829 | * permission. 830 | * 831 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 832 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 833 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 834 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 835 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 836 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 837 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 838 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 839 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 840 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 841 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 842 | * POSSIBILITY OF SUCH DAMAGE. 843 | ********************************************************************/ 844 | 845 | // Authors: myyerrol 846 | // Created: 2016.4.15 847 | ``` 848 | 849 | 文件内容: 850 | 851 | 紧接着版权许可和作者信息之后,每个文件都要用注释描述文件内容。 852 | 853 | 通常`.h` 文件要对所声明的类的功能和用法作简单说明。`.cpp` 文件通常包含了更多的实现细节或算法技巧讨论。如果你感觉这些实现细节或算法技巧讨论对于理解 `.h` 文件有帮助,可以将该注释挪到 `.h`,并在 `.cpp` 中指出文档在 `.h`。 854 | 855 | 不要简单的在 `.h` 和 `.cpp` 间复制注释,这种偏离了注释的实际意义。 856 | 857 | ### 6.3 类注释 858 | 859 | > **Tip** 860 | > 861 | > 每个类的定义都要附带一份注释,描述类的功能和用法。 862 | 863 | ``` cpp 864 | // Iterates over the contents of a GargantuanTable. 865 | class GargantuanTableIterator 866 | { 867 | ... 868 | }; 869 | ``` 870 | 871 | ### 6.4 函数注释 872 | 873 | > **Tip** 874 | > 875 | > 函数声明处注释描述函数功能,定义处描述函数实现。 876 | 877 | 函数声明: 878 | 879 | 注释位于声明之前,对函数功能及用法进行描述。注释使用叙述式而非指令式。注释只是为了描述函数,而不是命令函数做什么。通常,注释不会描述函数如何工作。那是函数定义部分的事情。 880 | 881 | 函数声明处注释的内容: 882 | 883 | - 函数的输入输出。 884 | - 对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数。 885 | - 如果函数分配了空间,需要由调用者释放。 886 | - 参数是否可以为 `NULL`。 887 | - 是否存在函数使用上的性能隐患。 888 | - 如果函数是可重入的,其同步前提是什么? 889 | 890 | 举例如下: 891 | 892 | ``` cpp 893 | // Returns an iterator for this table. It is the client's 894 | // responsibility to delete the iterator when it is done with it, 895 | // and it must not use the iterator once the GargantuanTable object 896 | // on which the iterator was created has been deleted. 897 | // 898 | // The iterator is initially positioned at the beginning of the table. 899 | // 900 | // This method is equivalent to: 901 | // Iterator* iter = table->NewIterator(); 902 | // iter->Seek(""); 903 | // return iter; 904 | // If you are going to immediately seek to another place in the 905 | // returned iterator, it will be faster to use NewIterator() 906 | // and avoid the extra seek. 907 | Iterator* GetIterator() const; 908 | ``` 909 | 910 | 但也要避免罗罗嗦嗦,或做些显而易见的说明。下面的注释就没有必要加上 "Returns false otherwise",因为已经暗含其中了: 911 | 912 | ``` cpp 913 | // Returns true if the table cannot hold any more entries. 914 | bool IsTableFull(); 915 | ``` 916 | 917 | 注释构造/析构函数时,切记读代码的人知道构造/析构函数是干啥的,所以 "Destroys this object" 这样的注释是没有意义的。注明构造函数对参数做了什么(例如,是否取得指针所有权)以及析构函数清理了什么。如果都是些无关紧要的内容,直接省掉注释。析构函数前没有注释是很正常的。 918 | 919 | 函数定义: 920 | 921 | 每个函数定义时要用注释说明函数功能和实现要点。比如说说你用的编程技巧,实现的大致步骤,或解释如此实现的理由,为什么前半部分要加锁而后半部分不需要。 922 | 923 | 不要从 `.h` 文件或其他地方的函数声明处直接复制注释。简要重述函数功能是可以的,但注释重点要放在如何实现上。 924 | 925 | ### 6.5 变量注释 926 | 927 | > **Tip** 928 | > 929 | > 通常变量名本身足以很好说明变量用途。某些情况下,也需要额外的注释说明。 930 | 931 | 类数据成员: 932 | 933 | 每个类数据成员(也叫实例变量或成员变量)都应该用注释说明用途。如果变量可以接受 `NULL` 或 `-1` 等警戒值,须加以说明。比如: 934 | 935 | ``` cpp 936 | private: 937 | // Keeps track of the total number of entries in the table. 938 | // Used to ensure we do not go over the limit. -1 means 939 | // that we don't yet know how many entries the table has. 940 | int num_total_entries_; 941 | ``` 942 | 943 | 全局变量: 944 | 945 | 和数据成员一样,所有全局变量也要注释说明含义及用途。比如: 946 | 947 | ``` cpp 948 | // The total number of tests cases that we run through in this regression test. 949 | const int kNumTestCases = 6; 950 | ``` 951 | 952 | ### 6.6 实现注释 953 | 954 | > **Tip** 955 | > 956 | > 对于代码中巧妙的,晦涩的,有趣的,重要的地方加以注释。 957 | 958 | 代码前注释: 959 | 960 | 巧妙或复杂的代码段前要加注释。比如: 961 | 962 | ``` cpp 963 | // Divide result by two, taking into account that x 964 | // contains the carry from the add. 965 | for (int i = 0; i < result->size(); i++) 966 | { 967 | x = (x << 8) + (*result)[i]; 968 | (*result)[i] = x >> 1; 969 | x &= 1; 970 | } 971 | ``` 972 | 973 | ### 6.7 标点, 拼写和语法 974 | 975 | > **Tip** 976 | > 977 | > 注意标点,拼写和语法。写的好的注释比差的要易读的多。 978 | 979 | 注释的通常写法是包含正确大小写和结尾句号的完整语句。短一点的注释(如代码行尾注释)可以随意点,依然要注意风格的一致性。完整的语句可读性更好,也可以说明该注释是完整的,而不是一些不成熟的想法。 980 | 981 | 虽然被别人指出该用分号时却用了逗号多少有些尴尬,但清晰易读的代码还是很重要的。正确的标点,拼写和语法对此会有所帮助。 982 | 983 | 984 | ## 7. 格式 985 | 986 | 代码风格和格式确实比较随意,但一个项目中所有人遵循同一风格是非常容易的。个体未必同意下述每一处格式规则,但整个项目服从统一的编程风格是很重要的,只有这样才能让所有人能很轻松的阅读和理解代码。 987 | 988 | ### 7.1 行长度 989 | 990 | > **Tip** 991 | > 992 | > 每一行代码字符数不超过 80。 993 | 994 | 我们也认识到这条规则是有争议的,但很多已有代码都已经遵照这一规则,我们感觉一致性更重要。 995 | 996 | 优点: 997 | 提倡该原则的人主张强迫他们调整编辑器窗口大小很野蛮。很多人同时并排开几个代码窗口,根本没有多余空间拉伸窗口。大家都把窗口最大尺寸加以限定,并且 80 列宽是传统标准。为什么要改变呢? 998 | 999 | 缺点: 1000 | 反对该原则的人则认为更宽的代码行更易阅读。80 列的限制是上个世纪 60 年代的大型机的古板缺陷。现代设备具有更宽的显示屏,很轻松的可以显示更多代码。 1001 | 1002 | 结论: 1003 | 80 个字符是最大值。 1004 | 1005 | 特例: 1006 | 1007 | - 如果一行注释包含了超过 80 字符的命令或 URL,出于复制粘贴的方便允许该行超过 80 字符。 1008 | - 包含长路径的 `#include` 语句可以超出80列。但应该尽量避免。 1009 | - 头文件保护,可以无视该原则. 1010 | 1011 | ### 7.2 非 ASCII 字符 1012 | 1013 | > **Tip** 1014 | > 1015 | > 尽量不使用非 ASCII 字符,使用时必须使用 UTF-8 编码。 1016 | 1017 | 即使是英文,也不应将用户界面的文本硬编码到源代码中,因此非 ASCII 字符要少用。特殊情况下可以适当包含此类字符。如,代码分析外部数据文件时,可以适当硬编码数据文件中作为分隔符的非 ASCII 字符串。更常见的是(不需要本地化的)单元测试代码可能包含非 ASCII 字符串。此类情况下,应使用 UTF-8 编码,因为很多工具都可以理解和处理 UTF-8 编码。 1018 | 1019 | ### 7.3 空格还是制表位 1020 | 1021 | > **Tip** 1022 | > 1023 | > 只使用空格,每次缩进 4 个空格。 1024 | 1025 | 我们使用空格缩进。不要在代码中使用制符表,你应该设置编辑器将制符表转为空格。 1026 | 1027 | ### 7.4 函数声明与定义 1028 | 1029 | > **Tip** 1030 | > 1031 | > 返回类型和函数名在同一行,参数也尽量放在同一行,如果放不下就对形参分行。 1032 | 1033 | 函数看上去像这样: 1034 | 1035 | ``` cpp 1036 | ReturnType ClassName::FunctionName(Type par_name, Type par_name2) 1037 | { 1038 | DoSomething(); 1039 | ... 1040 | } 1041 | ``` 1042 | 1043 | 如果同一行文本太多,放不下所有参数: 1044 | 1045 | ``` cpp 1046 | ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2, 1047 | Type par_name3) 1048 | { 1049 | DoSomething(); 1050 | ... 1051 | } 1052 | ``` 1053 | 1054 | 甚至连第一个参数都放不下: 1055 | 1056 | ``` cpp 1057 | ReturnType LongClassName::ReallyReallyReallyLongFunctionName( 1058 | Type par_name1, 1059 | Type par_name2, 1060 | Type par_name3) 1061 | { 1062 | DoSomething(); 1063 | ... 1064 | } 1065 | ``` 1066 | 1067 | 注意以下几点: 1068 | 1069 | - 如果返回类型和函数名在一行放不下,分行。 1070 | - 如果返回类型那个与函数声明或定义分行了,不要缩进。 1071 | - 左圆括号总是和函数名在同一行。 1072 | - 函数名和左圆括号间没有空格。 1073 | - 圆括号与参数间没有空格。 1074 | - 如果其它风格规则允许的话,右大括号总是单独位于函数最后一行,或者与左大括号同一行。 1075 | - 右大括号和左大括号间总是有一个空格。 1076 | - 函数声明和定义中的所有形参必须有命名且一致。 1077 | - 所有形参应尽可能对齐。 1078 | - 换行后的参数保持 4 个空格的缩进。 1079 | 1080 | 如果有些参数没有用到,在函数定义处将参数名注释起来: 1081 | 1082 | ``` cpp 1083 | // 正确:接口中形参恒有命名。 1084 | class Shape 1085 | { 1086 | public: 1087 | virtual void Rotate(double radians) = 0; 1088 | } 1089 | 1090 | // 正确:声明中形参恒有命名。 1091 | class Circle : public Shape 1092 | { 1093 | public: 1094 | virtual void Rotate(double radians); 1095 | } 1096 | 1097 | // 正确:定义中注释掉无用变量。 1098 | void Circle::Rotate(double /*radians*/) {} 1099 | ``` 1100 | 1101 | > **Warning** 1102 | 1103 | ``` cpp 1104 | // 警告:如果将来有人要实现,很难猜出变量是干什么用的。 1105 | void Circle::Rotate(double) {} 1106 | ``` 1107 | 1108 | ### 7.5 函数调用 1109 | 1110 | > **Tip** 1111 | > 1112 | > 要么一行写完函数调用,要么在圆括号里对参数分行,要么参数另起一行且缩进四格。如果没有其它顾虑的话,尽可能精简行数,比如把多个参数适当地放在同一行里。 1113 | > 1114 | > 函数调用遵循如下形式: 1115 | 1116 | ``` cpp 1117 | bool retval = DoSomething(argument1, argument2, argument3); 1118 | ``` 1119 | 1120 | 如果同一行放不下,可断为多行,后面每一行都和第一个实参对齐,左圆括号后和右圆括号前不要留空格: 1121 | 1122 | ``` cpp 1123 | bool retval = DoSomething(averyveryveryverylongargument1, 1124 | argument2, argument3); 1125 | ``` 1126 | 1127 | 参数也可以放在次行,缩进四格: 1128 | 1129 | ``` cpp 1130 | if (...) 1131 | { 1132 | DoSomething( 1133 | argument1, argument2, 1134 | argument3, argument4); 1135 | } 1136 | ``` 1137 | 1138 | 把多个参数放在同一行,是为了减少函数调用所需的行数,除非影响到可读性。有人认为把每个参数都独立成行,不仅更好读,而且方便编辑参数。不过,比起所谓的参数编辑,我们更看重可读性,且后者比较好办。 1139 | 1140 | 如果一些参数本身就是略复杂的表达式,且降低了可读性。那么可以直接创建临时变量描述该表达式,并传递给函数: 1141 | 1142 | ``` cpp 1143 | int my_heuristic = scores[x] * y + bases[x]; 1144 | bool retval = DoSomething(my_heuristic, x, y, z); 1145 | ``` 1146 | 1147 | 如果某参数独立成行,对可读性更有帮助的话,就这么办。 1148 | 1149 | 此外,如果一系列参数本身就有一定的结构,可以酌情地按其结构来决定参数格式: 1150 | 1151 | ``` cpp 1152 | // 通过 3x3 矩阵转换 widget。 1153 | my_widget.Transform(x1, x2, x3, 1154 | y1, y2, y3, 1155 | z1, z2, z3); 1156 | ``` 1157 | 1158 | ### 7.6 条件语句 1159 | 1160 | > **Tip** 1161 | > 1162 | > 倾向于不在圆括号内使用空格。关键字 `if` 和 `else` 另起一行。 1163 | 1164 | 最常见的是没有空格的格式。哪种都可以,但保持一致性。如果你是在修改一个文件, 参考当前已有格式。如果是写新的代码, 参考目录下或项目中其它文件。还在徘徊的话,就不要加空格了。 1165 | 1166 | ``` cpp 1167 | // 正确:圆括号里没空格紧邻。 1168 | if (condition) 1169 | { 1170 | ... 1171 | } 1172 | else 1173 | { 1174 | ... 1175 | } 1176 | ``` 1177 | 1178 | 注意所有情况下 `if` 和左圆括号间都有个空格。右圆括号和左大括号之间也要有个空格: 1179 | 1180 | > **Warning** 1181 | 1182 | ``` cpp 1183 | // 警告:if 后面没空格。 1184 | if(condition) 1185 | ``` 1186 | 1187 | 如果能增强可读性,简短的条件语句允许写在同一行。只有当语句简单并且没有使用 `else` 子句时使用: 1188 | 1189 | ``` cpp 1190 | if (x == kFoo) return new Foo(); 1191 | if (x == kBar) return new Bar(); 1192 | ``` 1193 | 1194 | 如果语句有 `else` 分支则不允许: 1195 | 1196 | > **Warning** 1197 | 1198 | ``` cpp 1199 | // 警告:当有 else 分支时,if 块却只有一行。 1200 | if (x) DoThis(); 1201 | else DoThat(); 1202 | ``` 1203 | 1204 | 要求 `if-else` 必须总是使用大括号,哪怕`if`或`else`语句只有一行: 1205 | 1206 | ``` cpp 1207 | if (condition) 1208 | { 1209 | DoSomething(); 1210 | } 1211 | else 1212 | { 1213 | DoSomething(); 1214 | } 1215 | ``` 1216 | 1217 | ### 7.7 循环和开关选择语句 1218 | 1219 | > **Tip** 1220 | > 1221 | > `switch` 语句必须使用大括号分段,以表明 `case` 之间不是连在一起的。在单语句循环里,括号必须使用。空循环体应使用 `{}` 或 `continue`。 1222 | 1223 | `switch` 语句中的 `case` 块要使用大括号。 1224 | 1225 | 如果有不满足 `case` 条件的枚举值,`switch` 应该总是包含一个 `default` 匹配(如果有输入值没有 `case` 去处理, 编译器将报警)。如果 `default` 应该永远执行不到,简单的加条 `assert`。 1226 | 1227 | ``` cpp 1228 | switch (var) 1229 | { 1230 | case 0: 1231 | { ... 1232 | break; 1233 | } 1234 | default: 1235 | { 1236 | assert(false); 1237 | } 1238 | } 1239 | ``` 1240 | 1241 | 在单语句循环里,括号也必须使用: 1242 | 1243 | ``` cpp 1244 | for (int i = 0; i < kSomeNumber; ++i) 1245 | { 1246 | printf("I take it back\n"); 1247 | } 1248 | ``` 1249 | 1250 | 空循环体应使用 `{}` 或 `continue`,而不是一个简单的分号。 1251 | 1252 | ``` cpp 1253 | // 正确:反复循环直到条件失效。 1254 | while (condition) {} 1255 | // 正确:空循环体。 1256 | for (int i = 0; i < kSomeNumber; ++i) {} 1257 | // 正确:contunue 表明没有逻辑。 1258 | while (condition) {continue}; 1259 | ``` 1260 | 1261 | > **Warning** 1262 | 1263 | ``` cpp 1264 | // 警告:看起来仅仅只是 while 的部分之一。 1265 | while (condition); 1266 | ``` 1267 | 1268 | ### 7.8 指针和引用表达式 1269 | 1270 | > **Tip** 1271 | > 1272 | > 句点或箭头前后不要有空格。指针/地址操作符(`*, &`)之后不能有空格. 1273 | 1274 | 下面是指针和引用表达式的正确使用范例: 1275 | 1276 | ``` cpp 1277 | x = *p; 1278 | p = &x; 1279 | x = r.y; 1280 | x = r->y; 1281 | ``` 1282 | 1283 | 注意: 1284 | 1285 | - 在访问成员时,句点或箭头前后没有空格。 1286 | - 指针操作符 `*` 或 `&` 后没有空格。 1287 | 1288 | 在声明指针变量或参数时,星号要与变量名紧挨着。 1289 | 1290 | ``` cpp 1291 | // 正确:空格前置。 1292 | char *c; 1293 | const string &str; 1294 | ``` 1295 | 1296 | > **Warning** 1297 | 1298 | ``` cpp 1299 | // 警告:* 两边都有空格。 1300 | char * c; 1301 | // 警告:& 两边都有空格。 1302 | const string & str; 1303 | ``` 1304 | 1305 | 在单个文件内要保持风格一致,所以,如果是修改现有文件,要遵照该文件的风格。 1306 | 1307 | ### 7.9 布尔表达式 1308 | 1309 | > **Tip** 1310 | > 1311 | > 如果一个布尔表达式超过标准行宽,断行方式要统一一下。 1312 | 1313 | 下例中, 逻辑与 (`&&`) 操作符总位于行尾: 1314 | 1315 | ``` cpp 1316 | if (this_one_thing > this_other_thing && 1317 | a_third_thing == a_fourth_thing && 1318 | yet_another & last_one) 1319 | { 1320 | ... 1321 | } 1322 | ``` 1323 | 1324 | 注意,上例的逻辑与 (`&&`) 操作符均位于行尾。合理使用的话对增强可读性是很有帮助的。此外直接用符号形式的操作符,比如 `&&` 和 `~`,不要用词语形式的 `and` 和 `compl`。 1325 | 1326 | ### 7.10 函数返回值 1327 | 1328 | > **Tip** 1329 | > 1330 | > `return` 表达式里时没必要都用圆括号。 1331 | 1332 | 假如您写 `x = epr` 时本来就会加上括号,那 `return expr;` 也可如法炮制。 1333 | 1334 | 函数返回时不要使用圆括号: 1335 | 1336 | ``` cpp 1337 | // 正确:返回值很简单,没有圆括号。 1338 | return result; 1339 | // 正确:可以用圆括号把复杂表达式圈起来,改善可读性。 1340 | return (some_long_condition && 1341 | another_condition); 1342 | ``` 1343 | 1344 | > **Warning** 1345 | 1346 | ``` cpp 1347 | // 警告:毕竟您从来不会写 var = (value); 1348 | return (value); 1349 | // 警告:return 可不是函数! 1350 | return(result); 1351 | ``` 1352 | 1353 | ### 7.11 变量及数组初始化 1354 | 1355 | > **Tip** 1356 | > 1357 | > 用 `=`、`()`、`{}` 均可。 1358 | 1359 | 您可以用 `=`、`()`、`{}`,以下都对: 1360 | 1361 | ``` cpp 1362 | int x = 3; 1363 | int x(3); 1364 | int x{3}; 1365 | string name("Some Name"); 1366 | string name = "Some Name"; 1367 | string name{"Some Name"}; 1368 | ``` 1369 | 1370 | 请务必小心列表初始化 {...} 用 `std::initializer_list` 构造函数初始化出的类型。非空列表初始化就会优先调用 `std::initializer_list`,不过空列表初始化除外,后者原则上会调用默认构造函数。为了强制禁用 `std::initializer_list` 构造函数,请改用括号。 1371 | 1372 | ``` cpp 1373 | vector v(100, 1); 1374 | vector v{100, 1}; 1375 | ``` 1376 | 1377 | 此外,列表初始化不允许整型类型的四舍五入,这可以用来避免一些类型上的编程失误。 1378 | 1379 | ``` cpp 1380 | // 正确:pi == 3。 1381 | int pi(3.14); 1382 | // 错误:不合适的转换。 1383 | int pi{3.14}; 1384 | ``` 1385 | 1386 | ### 7.12 预处理指令 1387 | 1388 | > **Tip** 1389 | > 1390 | > 预处理指令不要缩进,从行首开始。 1391 | 1392 | 即使预处理指令位于缩进代码块中,指令也应从行首开始。 1393 | 1394 | ``` cpp 1395 | if (lopsided_score) 1396 | { 1397 | // 正确:从行开头起。 1398 | #if DISASTER_PENDING 1399 | DropEverything(); 1400 | #endif 1401 | BackToNormal(); 1402 | } 1403 | ``` 1404 | 1405 | **Warning** 1406 | 1407 | ``` cpp 1408 | if (lopsided_score) 1409 | { 1410 | // 警告:"#if" 应该放在行开头。 1411 | #if DISASTER_PENDING 1412 | DropEverything(); 1413 | // 警告:"#endif" 不要缩进。 1414 | #endif 1415 | BackToNormal(); 1416 | } 1417 | ``` 1418 | 1419 | ### 7.13 类格式 1420 | 1421 | > **Tip** 1422 | > 1423 | > 访问控制块的声明依次序是 `public:`、`protected:`、`private:`,没有缩进。 1424 | 1425 | 类声明的基本格式如下: 1426 | 1427 | ``` cpp 1428 | class MyClass : public OtherClass 1429 | { 1430 | public: 1431 | MyClass(); 1432 | ~MyClass() {} 1433 | void SomeFunction(); 1434 | void SomeFunctionThatDoesNothing() {} 1435 | void set_some_var(int var) { some_var_ = var; } 1436 | int some_var() const { return some_var_; } 1437 | private: 1438 | bool SomeInternalFunction(); 1439 | private: 1440 | int some_var_; 1441 | int some_other_var_; 1442 | }; 1443 | ``` 1444 | 1445 | 注意事项: 1446 | 1447 | - 所有基类名应在 80 列限制下尽量与子类名放在同一行。 1448 | - 关键词 `public:`、`protected:`、`private:` 没有缩进。 1449 | - 关键词块之间不要有空格。 1450 | - 函数与变量如果属于同一个访问控制权限,则要分别写。顺序是:先函数,后变量(如上面例子中最后两个 private)。 1451 | - 这些关键词后不要保留空行。 1452 | - `public` 放在最前面, 然后是 `protected`, 最后是 `private`. 1453 | 1454 | ### 7.14 构造函数初始值列表 1455 | 1456 | > **Tip** 1457 | > 1458 | > 构造函数初始值列表按四格缩进并排几行。 1459 | 1460 | ``` cpp 1461 | // 断成多行,缩进四格,冒号放在第一行。 1462 | MyClass::MyClass(int var) 1463 | : some_var_(var), 1464 | some_other_var_(var + 1) 1465 | { 1466 | DoSomething(); 1467 | ... 1468 | } 1469 | ``` 1470 | 1471 | ### 7.15 水平留白 1472 | 1473 | > **Tip** 1474 | > 1475 | > 水平留白的使用因地制宜。永远不要在行尾添加没意义的留白。 1476 | 1477 | 常规: 1478 | 1479 | ``` cpp 1480 | // 分号前不加空格。 1481 | int i = 0; 1482 | // 大括号内部与空格紧邻。 1483 | int x[] = {0}; 1484 | // 继承与初始化列表中的冒号前后恒有空格。 1485 | class Foo : public Bar 1486 | { 1487 | public: 1488 | // 大括号里面是空的话,不加空格。 1489 | Foo(int b) : Bar(), baz_(b) {} 1490 | // 用空格把大括号与实现分开。 1491 | void Reset() { baz_ = 0; } 1492 | ... 1493 | } 1494 | ``` 1495 | 1496 | 添加冗余的留白会给其他人编辑时造成额外负担。因此,行尾不要留空格。如果确定一行代码已经修改完毕,将多余的空格去掉。或者在专门清理空格时去掉(确信没有其他人在处理)。 1497 | 1498 | 现在大部分代码编辑器稍加设置后,都支持自动删除行首/行尾空格,如果不支持,考虑换一款编辑器或 IDE。 1499 | 1500 | 循环和条件语句: 1501 | 1502 | ``` cpp 1503 | // if 、switch 和 for/while 关键字后均有空格。 1504 | if (b) {} 1505 | switch (i) {} 1506 | while (test) {} 1507 | for (int i = 0; i < 5; ++i) {} 1508 | switch (i) 1509 | { 1510 | // case 的冒号前无空格。 1511 | case 1: 1512 | ... 1513 | // case 的冒号后有代码,加个空格。 1514 | case 2: break; 1515 | ... 1516 | } 1517 | ``` 1518 | 1519 | 操作符: 1520 | 1521 | ``` cpp 1522 | // 赋值操作系统前后恒有空格。 1523 | x = 0; 1524 | 1525 | // 其它二元操作符也前后恒有空格。 1526 | // 圆括号内部不紧邻空格。 1527 | v = w * x + y / z; 1528 | v = w * (x + z); 1529 | 1530 | // 在参数和一元操作符之间不加空格。 1531 | x = -5; 1532 | ++x; 1533 | if (x && !y) 1534 | ``` 1535 | 1536 | 模板和转换: 1537 | 1538 | ``` cpp 1539 | // 尖括号(< and >)不与空格紧邻,< 前没有空格,>( 之间也没有。 1540 | vector x; 1541 | y = static_cast(x); 1542 | 1543 | // 在类型与指针操作符之间留空格也可以,但要保持一致。 1544 | vector x; 1545 | 1546 | set > x; 1547 | // 可以加上一对对称的空格。 1548 | set< list > x; 1549 | ``` 1550 | 1551 | ### 7.16 垂直留白 1552 | 1553 | > **Tip** 1554 | > 1555 | > 垂直留白越少越好。 1556 | 1557 | 这不仅仅是规则而是原则问题了:不在万不得已,不要使用空行。尤其是两个函数定义之间的空行不要超过 2 行(最好是1行),函数体首尾不要留空行,函数体中也不要随意添加空行。 1558 | 1559 | 基本原则是:同一屏可以显示的代码越多,越容易理解程序的控制流。当然,过于密集的代码块和过于疏松的代码块同样难看,取决于你的判断。但通常是垂直留白越少越好。 1560 | 1561 | 空行心得如下: 1562 | 1563 | - 函数体内开头或结尾的空行可读性微乎其微。 1564 | - 在多重 `if-else` 块里加空行或许有点可读性。 1565 | 1566 | 1567 | ## 8. 规则特例 1568 | 1569 | 前面说明的编程习惯基本都是强制性的。但所有优秀的规则都允许例外,这里就是探讨这些特例。 1570 | 1571 | ### 8.1 现有不合规范的代码 1572 | 1573 | > **Tip** 1574 | > 1575 | > 对于现有不符合既定编程风格的代码可以网开一面。 1576 | 1577 | 当你修改使用其他风格的代码时,为了与代码原有风格保持一致可以不使用本指南约定。如果不放心可以与代码原作者或现在的负责人员商讨。记住,一致性包括原有的一致性。 1578 | 1579 | ## 9. 结束语 1580 | 1581 | > **Tip** 1582 | > 1583 | > 运用常识和判断力,并保持一致。 1584 | 1585 | 编辑代码时,花点时间看看项目中的其它代码,并熟悉其风格。如果其它代码中 `if` 语句使用空格,那么你也要使用。如果其中的注释用星号 (\*) 围成一个盒子状,你同样要这么做。 1586 | 1587 | 风格指南的重点在于提供一个通用的编程规范,这样大家可以把精力集中在实现内容而不是表现形式上。我们展示了全局的风格规范,但局部风格也很重要,如果你在一个文件中新加的代码和原有代码风格相去甚远,这就破坏了文件本身的整体美观,也影响阅读,所以要尽量避免。 1588 | 1589 | 好了,关于编码风格写的够多了。代码本身才更有趣,尽情享受吧! 1590 | -------------------------------------------------------------------------------- /md/xm_developer_github_sytleguide.md: -------------------------------------------------------------------------------- 1 | # Team-Xmbot-Service-Robot Github风格指南 2 | 3 | ## 0. 前言 4 | 5 | ### 0.1 版本 6 | 7 | - 1.0版本(2016.5.1):缪宇飏(myyerrol)创建团队 Github 开发风格指南。本文档参考了很多网上的资料,并根据实际的需求,对内容进行了总结。新队员应该认真学习本指南,掌握 Github 基本的使用风格以及适用于本项目组的特定规则。如果有细节不统一的地方或者对本文档某处不是很认同,请在组内讨论统一之后,修改本指南。因为文档排版使用的是 Markdown 纯文本标记语言,也请后来者遵循本文档的开发方式,使用 Markdown 来修改、添加内容。 8 | 9 | ### 0.2 背景 10 | 11 | 对于 Team-Xmbot-Service-Robot(晓萌家庭服务机器人团队)开源项目来说,代码的管理至关重要。 12 | 13 | ## 1. GitHub 使用指南 14 | 15 | > **Tip** 16 | > 17 | > 请先自学 Git 和 GitHub。 18 | 19 | 本指南假设各位已经对 Git 和 GitHub 的使用与原理有了比较清晰的理解。如果还不会使用,请先自学 Git 和 GitHub 之后,再看本指南。 20 | 21 | 以下是推荐的学习资源: 22 | 23 | - [Git教程 - 掌握GitHub](http://www.worldhello.net/gotgithub/) 24 | - [Git教程 - 廖雪峰的官方网站](http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000) 25 | - [Git教程 - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html) 26 | - 书《GitHub入门与实践》 27 | - 书《Pro Git》 28 | 29 | ### 1.1 仓库 30 | 31 | > **Tip** 32 | > 33 | > 仓库中应该包括 README.md、.gitignore 和 LICENSE 等文件。 34 | 35 | 在创建本地 ROS 包仓库的时候,请在仓库中包含以下所需文件: 36 | 37 | - README.md:用来描述 ROS 包的功能、安装和使用方法等。编辑和修改此文件请使用 Markdown 语法。README.md 文件的内容最少应该由以下几点组成:包名、简介、功能、安装(可选)、使用。如果能有图片或架构图就更好了。具体例子请参考 [xm_arm_workspace](https://github.com/myyerrol/xm_arm_workspace)。 38 | - .gitignore:添加到这个名单中的文件,将不会被 Git 版本管理软件所管理。 39 | - LICENSE:用来描述 ROS 包发行所使用的软件开发协议。具体内容已经在 《ROS 风格指南》中进行了详细的介绍。 40 | 41 | ### 1.2 提交 42 | 43 | > **Tip** 44 | > 45 | > 在编写提交描述时,第一个单词首字母要大写。 46 | 47 | Yes: 48 | ``` bash 49 | git commit -m "Add xm_arm_workspace package" 50 | ``` 51 | 52 | No: 53 | ``` bash 54 | git commit -m "add xm_arm_workspace package" 55 | ``` 56 | 57 | ### 1.3 分支 58 | 59 | > **Tip** 60 | > 61 | > 请至少使用简单的 2 分支开发模式来管理 ROS 软件包。 62 | 63 | 分支是 Git 最为强大的地方,它是实现经典的 `Fork-Pull-Push` 开发模式的基础。具体如何创建、合并、推送、删除分支,请参考上面的学习资源。这里主要讨论代码版本的管理问题。 64 | 65 | - 2 分支版本管理模式是目前我们团队 GitHub 仓库代码的管理模式。它的原理比较简单:每次都将修改提交到默认的 master 分支上。换句话说,就是 master 分支既作为最新的开发分支,又作为稳定分支。当 master 分支上的代码被修改的足够多时,就可以创建一个新的分支来保留当前 master 分支的所有状态。以后 master 分支会继续不断地前进,而保留分支则保存了某些历史节点的代码修改。简单来说,保留分支是 master 分支在不同开发时间点的备份,它并不会被修改或更新。保留分支的命名格式为:年-月-ROS 版本-devel,举个例子:15.10-indigo-devel。以下为 2 分支模式图: 66 | 67 | ![github_branch_2 | center](../.images/github_branch_2.png) 68 | 69 | - 3 分支版本管理模式是在 2 分支的基础上的一个改进。即从原先 master 分支分出一个开发分支,master 分支只作为当前稳定分支而存在,而所有最新的代码修改都是在开发分支上完成的。只有当开发分支上的新功能趋于稳定且没有 Bug 的时候,才可以将开发分支合并到 master 稳定分支上,这样就能保证存在于最新代码中的问题不会影响到原先的老代码。保留分支的使用则和上面介绍的相同。以下为 3 分支模式图: 70 | 71 | ![github_branch_3 | center](../.images/github_branch_3.png) 72 | 73 | 结论:推荐使用 3 分支的 Git 开发模式。如果为了简化 GitHub 仓库的管理,使用 2 分支的模式也是可以的。 74 | 75 | ### 1.4 描述 76 | 77 | > **Tip** 78 | > 79 | > 为每个在 GitHub 上的代码仓库编写尽可能详细的描述。 80 | 81 | 举例: 82 | 83 | ![github_description](../.images/github_description.png) 84 | 85 | 86 | ## 2. GitHub 管理指南 87 | 88 | > **Tip** 89 | > 90 | > GitHub 组织管理员必看部分。 91 | 92 | 这部分主要是针对如何管理好我们整个团队 GitHub 组织仓库而写的。主要参考 [GitHub Help](https://help.github.com/categories/setting-up-and-managing-organizations-and-teams/) 官方资料。如果有哪位成员想成为 GitHub 组织管理员,一定要仔细阅读本章节。当然,其他成员也要认真学习才行。 93 | 94 | 95 | ### 2.1 组织 96 | 97 | > **Tip** 98 | > 99 | > 组织仓库管理的核心是权限。 100 | 101 | 组织是非常适合管理基于多人协同开发模式的开源项目的。组织管理主要是针对两个方面。一个是团队角色的权限管理,另一个是基于团队的代码仓库的权限管理。 102 | 103 | ### 2.1.1 角色权限 104 | 105 | > **Tip** 106 | > 107 | > 拥有者(Owners)的权限非常大,数量要被严格限制。 108 | 109 | 组织角色有两种。一个是拥有者(Owners),另一个是成员(Members)。 110 | 111 | - 拥有者(Owners):拥有组织管理的所有权限。因为其权限过于强大,所以这个角色数量必须被限定在很少的范围内。这里明确规定:**每届软件组里只允许有 1 个队员可以成为组织的拥有者**。 112 | - 成员(Members):组织的默认角色。拥有创建新团队(Team)、上传新代码、读写已有代码、可以在团队(Team)内部或之间相互交流和拥有团队维护者(Team Maintainer)头衔的权限。 113 | 114 | 其他具体请看 [GitHub 组织角色权限](https://help.github.com/articles/permission-levels-for-an-organization/) 介绍。 115 | 116 | ### 2.1.2 仓库权限 117 | 118 | > **Tip** 119 | > 120 | > 仓库的权限有:读、写、管理三种。 121 | 122 | 拥有者(Owners)和拥有团队维护者(Team Maintainer)权限的成员(Members)可以管理团队(Teams)内部仓库的访问权限。每一个团队(Team)都可以拥有不同的代码仓库以及对仓库的访问权限。成员(Members)可以获得对仓库的权限有: 123 | 124 | - 读(Read)。 125 | - 写(Write)。 126 | - 管理(Admin)。 127 | 128 | 组织的拥有者(Owners)可以添加任何仓库到任何团队(Teams)中去。而只拥有对某个仓库管理(Admin)权限的成员(Members)可以把这个仓库添加到他们所属的其他团队(Teams)中去。 129 | 130 | 一个组织的拥有者(Owner)可以将任何成员(Members)提升为一个或多个团队的维护者(Team Maintainer)。拥有团队维护者(Team Maintainer)头衔的成员(Members)可以获得权限为: 131 | 132 | - 改变团队(Team)的名字、描述和可视化属性。 133 | - 添加组织成员(Members)到团队(Team)中。 134 | - 删除团队中(Team)的组织成员(Members)。 135 | - 提升团队成员(Team Members)为团队维护者(Team Maintainers)。 136 | - 删除团队(Team)对已有仓库的访问权限。 137 | 138 | 组织的拥有者(Owners)可以删除组织中的任何仓库。如果组织的成员(Members)对某些仓库有管理(Admin)权限,那他们只可以删除这些仓库。 139 | 140 | 其他具体请看 [GitHub 组织仓库权限](https://help.github.com/articles/repository-permission-levels-for-an-organization/)介绍。 141 | 142 | **结论:以上介绍了很多概念,这里最后总结一下目前 GitHub 仓库的管理方案:** 143 | 144 | - **每届软件组里只允许有 1 个队员可以成为组织的拥有者**。 145 | - **每届每个团队(Team)里最多只能有 1 个团队维护者(Team Maintainers)。** 146 | - **每个队员对自己在组织新创建的代码仓库拥有最高的管理(Admin)权限。其他队员当然只对这个仓库拥有默认的只读权限。** 147 | - **由组织拥有者(Owners)负责添加仓库到相应的团队中去,并赋予仓库可以被写的权限。** 148 | - **每个新成员默认对组织中所有仓库的权限是只读的,而每个团队(Team)内部成员(Members)对添加到自己团队(Team)中的仓库是可写的(前提条件是上面那条)。** 149 | - **如果非常有必要,可以将团队(Team)中的某些代码仓库更改为管理(Admin)权限。但是不推荐这样做。** 150 | - **软件组新队员如果在规定提交代码仓库的一个月时间内,连一次提交记录都没有,就要被从组织中除名。** 151 | -------------------------------------------------------------------------------- /md/xm_developer_python_styleguide.md: -------------------------------------------------------------------------------- 1 | # Team-Xmbot-Service-Robot Python风格指南 2 | 3 | ## 0. 前言 4 | 5 | ### 0.1 版本 6 | 7 | - 1.0版本(2016.5.1):缪宇飏(myyerrol)创建团队 Python 代码开发风格指南。本文档参考了 ROS 和 Google 的 Python 风格指南,并根据实际的需求,对内容进行了适当的精简和改进。新队员应该认真学习本指南,掌握 Python 基本的开发风格。如果有细节不统一的地方或者对本文档某处不是很认同,请在组内讨论统一之后,修改本指南。因为文档排版使用的是 Markdown 纯文本标记语言,也请后来者遵循本文档的开发方式,使用 Markdown 来修改、添加内容。 8 | 9 | ### 0.2 背景 10 | 11 | Python 是 ROS 项目开发所使用的脚本语言。其在很多有关编写机器人策略的代码以及简单机械臂控制中应用的非常多(至少在组内是这个样子的)。尽管在语法上 Python 相较于 C++ 简单很多,但是很多惨痛的经历告诫我们,建立一套统一的 Python 代码风格是很有必要的。 12 | 13 | Team-Xmbot-Service-Robot(晓萌家庭服务机器人团队)作为开源项目,需要团队队员贡献代码,但是如果队员之间的代码编程风格不一致,便会给团队其他模块负责人造成不小的困扰。我们认为整洁、一致的代码风格可以使代码更加可读、更加可调试以及更加可维护。因此,我们应该编写优雅的代码使其不仅能够在现在发挥作用,而且在将来的若干年之后其依旧能够存在、可以被复用、或者是能够被未来的新队员改进。 14 | 15 | ## 1. Python 语言规范 16 | 17 | ### 1.1 导入 18 | 19 | > **Tip** 20 | > 21 | > 仅对包和模块使用导入。 22 | 23 | 定义: 24 | 模块间共享代码的重用机制。 25 | 26 | 优点: 27 | 命名空间管理约定十分简单。每个标识符的源都用一种一致的方式指示。x.Obj 表示 Obj 对象定义在模块 x 中。 28 | 29 | 缺点: 30 | 模块名仍可能冲突。有些模块名太长,不太方便。 31 | 32 | 结论: 33 | 使用 `import x` 来导入包和模块。 34 | 使用 `from x import y`,其中 x 是包前缀,y 是不带前缀的模块名。 35 | 使用 `from x import y as z`,如果两个要导入的模块都叫做 z 或者 y 太长了。 36 | 37 | 例如,模块 `sound.effects.echo` 可以用如下方式导入: 38 | 39 | ``` python 40 | from sound.effects import echo 41 | ... 42 | echo.EchoFilter(input, output, delay=0.7, atten=4) 43 | ``` 44 | 45 | 导入时不要使用相对名称。即使模块在同一个包中,也要使用完整包名。这能帮助你避免无意间导入一个包两次。 46 | 47 | ### 1.2 包 48 | 49 | > **Tip** 50 | > 51 | > 使用模块的全路径名来导入每个模块。 52 | 53 | 优点: 54 | 避免模块名冲突。查找包更容易。 55 | 56 | 缺点: 57 | 部署代码变难,因为你必须复制包层次。 58 | 59 | 结论: 60 | 所有的新代码都应该用完整包名来导入每个模块。 61 | 62 | 应该像下面这样导入: 63 | 64 | ``` python 65 | # Reference in code with complete name. 66 | import sound.effects.echo 67 | 68 | # Reference in code with just module name (preferred). 69 | from sound.effects import echo 70 | ``` 71 | 72 | ### 1.3 异常 73 | 74 | > **Tip** 75 | > 76 | > 允许使用异常,但必须小心。 77 | 78 | 定义: 79 | 异常是一种跳出代码块的正常控制流来处理错误或者其它异常条件的方式。 80 | 81 | 优点: 82 | 正常操作代码的控制流不会和错误处理代码混在一起。当某种条件发生时,它也允许控制流跳过多个框架。例如,一步跳出 N 个嵌套的函数,而不必继续执行错误的代码。 83 | 84 | 缺点: 85 | 可能会导致让人困惑的控制流。调用库时容易错过错误情况。 86 | 87 | 结论: 88 | 异常必须遵守特定条件: 89 | 90 | - 像这样触发异常: `raise MyException("Error message")` 或者 `raise MyException`。不要使用两个参数的形式( `raise MyException, "Error message"` )或者过时的字符串异常(`raise "Error message"`)。 91 | - 模块或包应该定义自己的特定域的异常基类,这个基类应该从内建的 `Exception` 类继承。模块的异常基类应该叫做 "Error"。 92 | 93 | ``` python 94 | class Error(Exception): 95 | pass 96 | ``` 97 | 98 | - 永远不要使用 `except:` 语句来捕获所有异常,也不要捕获 `Exception` 或者 `StandardError`,除非你打算重新触发该异常,或者你已经在当前线程的最外层(记得还是要打印一条错误消息)。 在异常这方面,Python 非常宽容,`except:` 真的会捕获包括 Python 语法错误在内的任何错误。使用 `except:` 很容易隐藏真正的 Bug。 99 | - 尽量减少 `try/except` 块中的代码量。`try` 块的体积越大,期望之外的异常就越容易被触发。这种情况下,`try/except` 块将隐藏真正的错误。 100 | - 使用 `finally` 子句来执行那些无论 `try` 块中有没有异常都应该被执行的代码。这对于清理资源常常很有用,例如关闭文件。 101 | - 当捕获异常时,使用 `as` 而不要用逗号。例如: 102 | 103 | ``` python 104 | try: 105 | raise Error 106 | except Error as error: 107 | pass 108 | ``` 109 | 110 | ### 1.4 全局变量 111 | 112 | > **Tip** 113 | > 114 | > 避免全局变量。 115 | 116 | 定义: 117 | 定义在模块级的变量。 118 | 119 | 优点: 120 | 偶尔有用。 121 | 122 | 缺点: 123 | 导入时可能改变模块行为,因为导入模块时会对模块级变量赋值。 124 | 125 | 结论: 126 | 避免使用全局变量,用类变量来代替。但也有一些例外: 127 | 128 | - 脚本的默认选项。 129 | - 模块级常量。例如:PI = 3.14159。常量应该全大写,用下划线连接。 130 | - 有时候用全局变量来缓存值或者作为函数返回值很有用。 131 | - 如果需要,全局变量应该仅在模块内部可用,并通过模块级的公共函数来访问。 132 | 133 | ### 1.5 列表推导 134 | 135 | > **Tip** 136 | > 137 | > 可以在简单情况下使用。 138 | 139 | 定义: 140 | 列表推导与生成器表达式提供了一种简洁高效的方式来创建列表和迭代器,而不必借助 `map()`、`filter()`、或者 `lambda`。 141 | 142 | 优点: 143 | 简单的列表推导可以比其它的列表创建方法更加清晰简单。生成器表达式可以十分高效,因为它们避免了创建整个列表。 144 | 145 | 缺点: 146 | 复杂的列表推导或者生成器表达式可能难以阅读。 147 | 148 | 结论: 149 | 适用于简单情况。每个部分应该单独置于一行:映射表达式、for 语句、过滤器表达式。禁止多重 for 语句或过滤器表达式。复杂情况下还是使用循环。 150 | 151 | ``` python 152 | Yes: 153 | result = [] 154 | for x in range(10): 155 | for y in range(5): 156 | if x * y > 10: 157 | result.append((x, y)) 158 | 159 | for x in xrange(5): 160 | for y in xrange(5): 161 | if x != y: 162 | for z in xrange(5): 163 | if y != z: 164 | yield (x, y, z) 165 | 166 | return ((x, complicated_transform(x)) 167 | for x in long_generator_function(parameter) 168 | if x is not None) 169 | 170 | squares = [x * x for x in range(10)] 171 | 172 | eat(jelly_bean for jelly_bean in jelly_beans 173 | if jelly_bean.color == 'black') 174 | ``` 175 | 176 | ``` python 177 | No: 178 | result = [(x, y) for x in range(10) for y in range(5) if x * y > 10] 179 | 180 | return ((x, y, z) 181 | for x in xrange(5) 182 | for y in xrange(5) 183 | if x != y 184 | for z in xrange(5) 185 | if y != z) 186 | ``` 187 | 188 | ### 1.6 默认迭代器和操作符 189 | 190 | > **Tip** 191 | > 192 | > 如果类型支持,就使用默认迭代器和操作符。比如列表,字典及文件等。 193 | 194 | 定义: 195 | 容器类型,像字典和列表,定义了默认的迭代器和关系测试操作符。 196 | 197 | 优点: 198 | 默认操作符和迭代器简单高效,它们直接表达了操作,没有额外的方法调用。使用默认操作符的函数是通用的。它可以用于支持该操作的任何类型。 199 | 200 | 缺点: 201 | 你没法通过阅读方法名来区分对象的类型(例如 `has_key()` 意味着字典)。 不过这也是优点。 202 | 203 | 结论: 204 | 如果类型支持,就使用默认迭代器和操作符,例如列表,字典和文件。内建类型也定义了迭代器方法。优先考虑这些方法,而不是那些返回列表的方法。当然,这样遍历容器时,你将不能修改容器。 205 | 206 | ``` python 207 | Yes: for key in adict: ... 208 | if key not in adict: ... 209 | if obj in alist: ... 210 | for line in afile: ... 211 | for k, v in dict.iteritems(): ... 212 | ``` 213 | 214 | ``` python 215 | No: for key in adict.keys(): ... 216 | if not adict.has_key(key): ... 217 | for line in afile.readlines(): ... 218 | ``` 219 | 220 | ### 1.7 默认参数值 221 | 222 | > **Tip** 223 | > 224 | > 适用于大部分情况。 225 | 226 | 定义: 227 | 你可以在函数参数列表的最后指定变量的值,例如:`def foo(a, b = 0):` 。如果调用 `foo` 时只带一个参数,则 `b` 被设为 0。如果带两个参数,则 `b` 的值等于第二个参数。 228 | 229 | 优点: 230 | 你经常会碰到一些使用大量默认值的函数,但偶尔(比较少见)你想要覆盖这些默认值。默认参数值提供了一种简单的方法来完成这件事,你不需要为这些罕见的例外定义大量函数。同时,Python 也不支持重载方法和函数,默认参数是一种”仿造”重载行为的简单方式。 231 | 232 | 缺点: 233 | 默认参数只在模块加载时求值一次。如果参数是列表或字典之类的可变类型,这可能会导致问题。如果函数修改了对象(例如向列表追加项),默认值就被修改了。 234 | 235 | 结论: 236 | 鼓励使用,不过有如下注意事项: 237 | 238 | 不要在函数或方法定义中使用可变对象作为默认值。 239 | 240 | ``` python 241 | Yes: def foo(a, b=None): 242 | if b is None: 243 | b = [] 244 | ``` 245 | 246 | ``` python 247 | No: def foo(a, b=[]): 248 | ... 249 | No: def foo(a, b=time.time()): # The time the module was loaded? 250 | ... 251 | No: def foo(a, b=FLAGS.my_thing): # sys.argv has not yet been parsed... 252 | ... 253 | ``` 254 | 255 | ### 1.8 True/False 的求值 256 | 257 | > **Tip** 258 | > 259 | > 尽可能使用隐式 `false`。 260 | 261 | 定义: 262 | Python 在布尔上下文中会将某些值求值为 `false`。按简单的直觉来讲,就是所有的”空“值都被认为是 `false`。因此 `0`、`None`、`[]`、`{}`、`""` 都被认为是 `false`。 263 | 264 | 优点: 265 | 使用 Python 布尔值的条件语句更易读也更不易犯错。大部分情况下,也更快。 266 | 267 | 缺点: 268 | 对 C/C++ 开发人员来说,可能看起来有点怪。 269 | 270 | 结论: 271 | 尽可能使用隐式的 `false`,例如: 使用 `if foo:` 而不是 `if foo != []:`。 不过还是有一些注意事项需要你铭记在心: 272 | 273 | - 永远不要用 `==` 或者 `!=` 来比较单件,比如 `None`。使用 `is` 或者 `is not`。 274 | - 注意:当你写下 `if x:` 时,你其实表示的是 `if x is not None`。例如:当你要测试一个默认值是 `None` 的变量或参数是否被设为其它值。这个值在布尔语义下可能是 `false`! 275 | - 永远不要用 `==` 将一个布尔量与 `false` 相比较。使用 `if not x:` 代替。如果你需要区分 `false` 和 `None`,你应该用像 `if not x and x is not None:` 这样的语句。 276 | - 对于序列(字符串、列表、元组),要注意空序列是 `false`。因此 `if not seq:` 或者 `if seq:` 比 `if len(seq):` 或 `if not len(seq):` 要更好。 277 | - 处理整数时,使用隐式 `false` 可能会得不偿失(即不小心将 `None` 当做 0 来处理)。你可以将一个已知是整型(且不是 `len()` 的返回结果)的值与 0 比较。 278 | 279 | ``` python 280 | Yes: if not users: 281 | print 'no users' 282 | 283 | if foo == 0: 284 | self.handle_zero() 285 | 286 | if i % 10 == 0: 287 | self.handle_mulTiple_of_ten() 288 | ``` 289 | 290 | ``` python 291 | No: if len(users) == 0: 292 | print 'no users' 293 | 294 | if foo is not None and not foo: 295 | self.handle_zero() 296 | 297 | if not i % 10: 298 | self.handle_mulTiple_of_ten() 299 | ``` 300 | 301 | ### 1.9 过时的语言特性 302 | 303 | > **Tip** 304 | > 305 | > 尽可能使用字符串方法取代字符串模块。使用函数调用语法取代 `apply()`。使用列表推导,`for` 循环取代 `filter()`,`map()` 以及 `reduce()`。 306 | 307 | 定义: 308 | 当前版本的 Python 提供了大家通常更喜欢的替代品。 309 | 310 | 结论: 311 | 我们不使用不支持这些特性的 Python 版本,所以没理由不用新的方式。 312 | 313 | ``` python 314 | Yes: words = foo.split(':') 315 | 316 | [x[1] for x in my_list if x[2] == 5] 317 | 318 | map(math.sqrt, data) 319 | 320 | fn(*args, **kwargs) 321 | ``` 322 | 323 | ``` python 324 | No: words = string.split(foo, ':') 325 | 326 | map(lambda x: x[1], filter(lambda x: x[2] == 5, my_list)) 327 | 328 | apply(fn, args, kwargs) 329 | ``` 330 | 331 | ### 1.10 威力过大的特性 332 | 333 | > **Tip** 334 | > 335 | > 避免使用这些特性。 336 | 337 | 定义: 338 | Python 是一种异常灵活的语言,它为你提供了很多花哨的特性。诸如元类、字节码访问、任意编译、动态继承、对象父类重定义、导入黑客、反射、系统内修改等等。 339 | 340 | 优点: 341 | 强大的语言特性,能让你的代码更紧凑。 342 | 343 | 缺点: 344 | 使用这些很”酷“的特性十分诱人,但不是绝对必要。使用新特性的代码将更加难以阅读和调试。开始可能还好(对原作者而言),但当你回顾代码,它们可能会比那些稍长一点但是很直接的代码更加难以理解。 345 | 346 | 结论: 347 | 在你的代码中避免这些特性。 348 | 349 | ## 2. Python 风格规范 350 | 351 | ### 2.1 分号 352 | 353 | > **Tip** 354 | > 355 | > 不要在行尾加分号,也不要用分号将两条命令放在同一行。 356 | 357 | ### 2.2 行长度 358 | 359 | > **Tip** 360 | > 361 | > 每行不超过 80 个字符。 362 | 363 | 例外: 364 | 365 | - 长的导入模块语句。 366 | - 注释里的 URL。 367 | 368 | 不要使用反斜杠连接行。 369 | 370 | Python 会将圆括号、中括号和花括号中的行隐式的连接起来,你可以利用这个特点。如果需要,你可以在表达式外围增加一对额外的圆括号。 371 | 372 | ``` python 373 | Yes: foo_bar(self, width, height, color='black', design=None, x='foo', 374 | emphasis=None, highlight=0) 375 | 376 | if (width == 0 and height == 0 and 377 | color == 'red' and emphasis == 'strong'): 378 | ``` 379 | 380 | 如果一个文本字符串在一行放不下,可以使用圆括号来实现隐式行连接: 381 | 382 | ``` python 383 | x = ('This will build a very long long ' 384 | 'long long long long long long string') 385 | ``` 386 | 387 | 在注释中,如果必要,将长的 URL 放在一行上。 388 | 389 | ``` python 390 | Yes: # See details at 391 | # http://www.example.com/us/developer/documentation/api/content/v2.0/csv_file_name_extension_full_specification.html 392 | ``` 393 | 394 | ``` python 395 | No: # See details at 396 | # http://www.example.com/us/developer/documentation/api/content/\ 397 | # v2.0/csv_file_name_extension_full_specification.html 398 | ``` 399 | 400 | ### 2.3 括号 401 | 402 | > **Tip** 403 | > 404 | > 宁缺毋滥地使用括号。 405 | 406 | 除非是用于实现行连接,否则不要在返回语句或条件语句中使用括号。不过在元组两边使用括号是可以的。 407 | 408 | ``` python 409 | Yes: if foo: 410 | bar() 411 | while x: 412 | x = bar() 413 | if x and y: 414 | bar() 415 | if not x: 416 | bar() 417 | return foo 418 | for (x, y) in dict.items(): ... 419 | ``` 420 | 421 | ``` python 422 | No: if (x): 423 | bar() 424 | if not(x): 425 | bar() 426 | return (foo) 427 | ``` 428 | 429 | ### 2.4 缩进 430 | 431 | > **Tip** 432 | > 433 | > 用 4 个空格来缩进代码。 434 | 435 | 绝对不要用 tab,也不要 tab 和空格混用。对于行连接的情况,你应该要么垂直对齐换行的元素,或者使用 4 空格的悬挂式缩进(这时第一行不应该有参数): 436 | 437 | ``` python 438 | Yes: # Aligned with opening delimiter. 439 | foo = long_function_name(var_one, var_two, 440 | var_three, var_four) 441 | 442 | # Aligned with opening delimiter in a dictionary. 443 | foo = { 444 | long_dictionary_key: value1 + 445 | value2, 446 | ... 447 | } 448 | 449 | # 4-space hanging indent, nothing on first line. 450 | foo = long_function_name( 451 | var_one, var_two, var_three, 452 | var_four) 453 | ``` 454 | 455 | ``` python 456 | No: # Stuff on first line forbidden. 457 | foo = long_function_name(var_one, var_two, 458 | var_three, var_four) 459 | 460 | # 2-space hanging indent forbidden. 461 | foo = long_function_name( 462 | var_one, var_two, var_three, 463 | var_four) 464 | } 465 | ``` 466 | 467 | ### 2.5 空行 468 | 469 | > **Tip** 470 | > 471 | > 顶级定义之间空两行,方法定义之间空一行。 472 | 473 | 顶级定义之间空两行,比如函数或者类定义。方法定义、类定义与第一个方法之间都应该空一行。函数或方法中,某些地方要是你觉得合适,就空一行。 474 | 475 | ### 2.6 空格 476 | 477 | > **Tip** 478 | > 479 | > 按照标准的排版规范来使用标点两边的空格。 480 | 481 | 括号内不要有空格。 482 | 483 | ``` python 484 | Yes: spam(ham[1], {eggs: 2}, []) 485 | ``` 486 | 487 | ``` python 488 | No: spam( ham[ 1 ], { eggs: 2 }, [ ] ) 489 | ``` 490 | 491 | 不要在逗号、分号、冒号前面加空格,但应该在它们后面加(除了在行尾)。 492 | 493 | ``` python 494 | Yes: if x == 4: 495 | print x, y 496 | x, y = y, x 497 | ``` 498 | 499 | ``` python 500 | No: if x == 4 : 501 | print x , y 502 | x , y = y , x 503 | ``` 504 | 505 | 参数列表,索引或切片的左括号前不应加空格。 506 | 507 | ``` python 508 | Yes: spam(1) 509 | ``` 510 | 511 | ``` python 512 | no: spam (1) 513 | ``` 514 | 515 | ``` python 516 | Yes: dict['key'] = list[index] 517 | ``` 518 | 519 | ``` python 520 | No: dict ['key'] = list [index] 521 | ``` 522 | 523 | 在二元操作符两边都加上一个空格,比如赋值(`=`), 比较(`==`、`<`、`>`、`!=`、`<>`、`<=`、`>=`、`in`、`not in`、`is`、`is not`), 布尔(`and`、`or`、`not`)。至于算术操作符两边的空格该如何使用,需要你自己好好判断。不过两侧务必要保持一致。 524 | 525 | ``` python 526 | Yes: x == 1 527 | ``` 528 | 529 | ``` python 530 | No: x<1 531 | ``` 532 | 533 | 当 `=` 用于指示关键字参数或默认参数值时,不要在其两侧使用空格。 534 | 535 | ``` python 536 | Yes: def complex(real, imag=0.0): return magic(r=real, i=imag) 537 | ``` 538 | 539 | ``` python 540 | No: def complex(real, imag = 0.0): return magic(r = real, i = imag) 541 | ``` 542 | 543 | 不要用空格来垂直对齐多行间的标记,因为这会成为维护的负担(适用于 `:`、`#`、`=`等): 544 | 545 | ``` python 546 | Yes: 547 | foo = 1000 # comment 548 | long_name = 2 # comment that should not be aligned 549 | 550 | dictionary = { 551 | "foo": 1, 552 | "long_name": 2, 553 | } 554 | ``` 555 | 556 | ``` python 557 | No: 558 | foo = 1000 # comment 559 | long_name = 2 # comment that should not be aligned 560 | 561 | dictionary = { 562 | "foo" : 1, 563 | "long_name": 2, 564 | } 565 | ``` 566 | 567 | ### 2.7 Shebang 568 | 569 | > **Tip** 570 | > 571 | > 程序第一行应该以 `#!/usr/bin/python2.7` 开始。Python 版本号具体以安装在你计算机里的为准。 572 | 573 | 在计算机科学中,[Shebang](http://en.wikipedia.org/wiki/Shebang_(Unix)) (也称为Hashbang)是一个由井号和叹号构成的字符串行(`#!`),其出现在文本文件的第一行的前两个字符。在文件中存在 Shebang 的情况下,类 Unix 操作系统的程序载入器会分析 Shebang 后的内容,将这些内容作为解释器指令,并调用该指令,并将载有 Shebang 的文件路径作为该解释器的参数。例如,以指令 `#!/bin/sh` 开头的文件在执行时会实际调用 `/bin/sh` 程序。 574 | 575 | `#!` 先用于帮助内核找到 Python 解释器,但是在导入模块时,将会被忽略。因此只有被直接执行的文件中才有必要加入 `#!`。 576 | 577 | ### 2.8 注释 578 | 579 | > **Tip** 580 | > 581 | > 确保对模块、函数、方法和行内注释使用正确的风格。 582 | 583 | **文件头注释** 584 | 585 | > 每个 Python 文件的头部都需要添加文件头注释。 586 | > - 版权声明。 587 | > - 开发作者。 588 | > - 开发时间. 589 | 590 | 举个例子: 591 | 592 | ```python 593 | """ 594 | ******************************************************************** 595 | * Software License Agreement (BSD License) 596 | * 597 | * Copyright (c) 2016, Team-Xmbot-Service-Robot 598 | * All rights reserved. 599 | * 600 | * Redistribution and use in source and binary forms, with or without 601 | * modification, are permitted provided that the following conditions 602 | * are met: 603 | * 604 | * * Redistributions of source code must retain the above copyright 605 | * notice, this list of conditions and the following disclaimer. 606 | * * Redistributions in binary form must reproduce the above 607 | * copyright notice, this list of conditions and the following 608 | * disclaimer in the documentation and/or other materials provided 609 | * with the distribution. 610 | * * Neither the name of the Team-Xmbot-Service-Robot nor the names 611 | * of its contributors may be used to endorse or promote products 612 | * derived from this software without specific prior written 613 | * permission. 614 | * 615 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 616 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 617 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 618 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 619 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 620 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 621 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 622 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 623 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 624 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 625 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 626 | * POSSIBILITY OF SUCH DAMAGE. 627 | ******************************************************************** 628 | """ 629 | 630 | # Authors: myyerrol 631 | # Created: 2016.4.15 632 | ``` 633 | 634 | **文档字符串** 635 | 636 | > Python 有一种独一无二的的注释方式: 使用文档字符串。文档字符串是包、模块、类或函数里的第一个语句。这些字符串可以通过对象的 `__doc__` 成员被自动提取,并且被 `pydoc` 所用。我们对文档字符串的惯例是使用三重双引号 `"""`。一个文档字符串应该这样组织:首先是一行以句号,问号或惊叹号结尾的概述(或者该文档字符串单纯只有一行)。接着是一个空行,接着是文档字符串剩下的部分,它应该与文档字符串的第一行的第一个引号对齐。下面有更多文档字符串的格式化规范。 637 | 638 | **模块** 639 | 640 | > 每个文件应该包含一个许可证。根据项目使用的许可(BSD)。 641 | 642 | **函数和方法** 643 | 644 | > 下文所指的函数,包括函数、方法、以及生成器。 645 | > 646 | > 一个函数必须要有文档字符串,除非它满足以下条件: 647 | > 648 | > - 外部不可见。 649 | > - 非常短小。 650 | > - 简单明了。 651 | > 652 | > 文档字符串应该包含函数做什么,以及输入和输出的详细描述。通常,不应该描述”怎么做“,除非是一些复杂的算法。文档字符串应该提供足够的信息,当别人编写代码调用该函数时,他不需要看一行代码,只要看文档字符串就可以了。对于复杂的代码,在代码旁边加注释会比使用文档字符串更有意义。 653 | > 654 | > 关于函数的几个方面应该在特定的小节中进行描述记录,这几个方面如下文所述。每节应该以一个标题行开始。标题行以冒号结尾。除标题行外,节的其他内容应被缩进 2 个空格。 655 | > 656 | > Args: 657 | > 列出每个参数的名字,并在名字后使用一个冒号和一个空格,分隔对该参数的描述。如果描述太长超过了单行 80 字符,使用 2 或者 4 个空格的悬挂缩进(与文件其他部分保持一致)。描述应该包括所需的类型和含义。如果一个函数接受 `*foo` (可变长度参数列表)或者 `**bar` (任意关键字参数),应该详细列出 `*foo` 和 `**bar`。 658 | > 659 | > Returns: 660 | > 描述返回值的类型和语义。如果函数返回 `None`, 这一部分可以省略。 661 | > 662 | > Raises: 663 | > 列出与接口有关的所有异常。 664 | 665 | ``` python 666 | def fetch_bigtable_rows(big_table, keys, other_silly_variable=None): 667 | """Fetches rows from a Bigtable. 668 | 669 | Retrieves rows pertaining to the given keys from the Table instance 670 | represented by big_table. Silly things may happen if 671 | other_silly_variable is not None. 672 | 673 | Args: 674 | big_table: An open Bigtable Table instance. 675 | keys: A sequence of strings representing the key of each table row 676 | to fetch. 677 | other_silly_variable: Another optional variable, that has a much 678 | longer name than the other args, and which does nothing. 679 | 680 | Returns: 681 | A dict mapping keys to the corresponding table row data 682 | fetched. Each row is represented as a tuple of strings. For 683 | example: 684 | 685 | {'Serak': ('Rigel VII', 'Preparer'), 686 | 'Zim': ('Irk', 'Invader'), 687 | 'Lrrr': ('Omicron Persei 8', 'Emperor')} 688 | If a key from the keys argument is missing from the dictionary, 689 | then that row was not found in the table. 690 | 691 | Raises: 692 | IOError: An error occurred accessing the bigtable.Table object. 693 | """ 694 | pass 695 | ``` 696 | 697 | **类** 698 | 699 | > 类应该在其定义下有一个用于描述该类的文档字符串。如果你的类有公共属性,那么文档中应该有一个属性段,并且应该遵守和函数参数相同的格式。 700 | 701 | ``` python 702 | class SampleClass(object): 703 | """Summary of class here. 704 | 705 | Longer class information.... 706 | Longer class information.... 707 | 708 | Attributes: 709 | likes_spam: A boolean indicating if we like SPAM or not. 710 | eggs: An integer count of the eggs we have laid. 711 | """ 712 | 713 | def __init__(self, likes_spam=False): 714 | """Inits SampleClass with blah.""" 715 | self.likes_spam = likes_spam 716 | self.eggs = 0 717 | 718 | def public_method(self): 719 | """Performs operation blah.""" 720 | ``` 721 | 722 | **块注释和行注释** 723 | 724 | > 最需要写注释的是代码中那些技巧性的部分。如果你在下次 [代码审查](http://en.wikipedia.org/wiki/Code_review) 的时候必须解释一下,那么你应该现在就给它写注释。对于复杂的操作,应该在其操作开始前写上若干行注释。对于不是一目了然的代码,应在其行尾添加注释。 725 | 726 | ``` python 727 | # We use a weighted dictionary search to find out where i is in 728 | # the array. We extrapolate position based on the largest num 729 | # in the array and the array size and then do binary search to 730 | # get the exact number. 731 | 732 | if i & (i-1) == 0: # true if i is a power of 2 733 | ``` 734 | 735 | > 为了提高可读性,注释应该至少离开代码 2 个空格。 736 | > 737 | > 另一方面,绝不要描述代码。假设阅读代码的人比你更懂 Python,他只是不知道你的代码要做什么。 738 | 739 | ``` python 740 | # BAD COMMENT: Now go through the b array and make sure whenever i occurs 741 | # the next element is i+1 742 | ``` 743 | 744 | ### 2.9 字符串 745 | 746 | > **Tip** 747 | > 748 | > 即使参数都是字符串,使用 `%` 操作符或者格式化方法格式化字符串。不过也不能一概而论,你需要在 `+` 和 `%` 之间好好判定。 749 | 750 | ``` python 751 | Yes: x = a + b 752 | x = '%s, %s!' % (imperative, expletive) 753 | x = '{}, {}!'.format(imperative, expletive) 754 | x = 'name: %s; score: %d' % (name, n) 755 | x = 'name: {}; score: {}'.format(name, n) 756 | ``` 757 | 758 | ``` python 759 | No: x = '%s%s' % (a, b) # use + in this case 760 | x = '{}{}'.format(a, b) # use + in this case 761 | x = imperative + ', ' + expletive + '!' 762 | x = 'name: ' + name + '; score: ' + str(n) 763 | ``` 764 | 765 | 避免在循环中用 `+` 和 `+=` 操作符来累加字符串。由于字符串是不可变的,这样做会创建不必要的临时对象,并且导致二次方而不是线性的运行时间。作为替代方案,你可以将每个子串加入列表,然后在循环结束后用 `.join` 连接列表。(也可以将每个子串写入一个 `cStringIO.StringIO` 缓存中)。 766 | 767 | ``` python 768 | Yes: items = [''] 769 | for last_name, first_name in employee_list: 770 | items.append('' % (last_name, first_name)) 771 | items.append('
%s, %s
') 772 | employee_table = ''.join(items) 773 | ``` 774 | 775 | ``` python 776 | No: employee_table = '' 777 | for last_name, first_name in employee_list: 778 | employee_table += '' % (last_name, first_name) 779 | employee_table += '
%s, %s
' 780 | ``` 781 | 782 | 在同一个文件中,保持使用字符串引号的一致性。使用单引号 `‘` 或者双引号 `"` 之一用以引用字符串,并在同一文件中沿用。在字符串内可以使用另外一种引号,以避免在字符串中使用。 783 | 784 | ``` python 785 | Yes: 786 | Python('Why are you hiding your eyes?') 787 | Gollum("I'm scared of lint errors.") 788 | Narrator('"Good!" thought a happy Python reviewer.') 789 | ``` 790 | 791 | ``` python 792 | No: 793 | Python("Why are you hiding your eyes?") 794 | Gollum('The lint. It burns. It burns us.') 795 | Gollum("Always the great lint. Watching. Watching.") 796 | ``` 797 | 798 | 为多行字符串使用三重双引号 `"""` 而非三重单引号 `'''`。当且仅当项目中使用单引号 `'` 来引用字符串时,才可能会使用三重 `'''` 为非文档字符串的多行字符串来标识引用。文档字符串必须使用三重双引号 `"""`。不过要注意,通常用隐式行连接更清晰,因为多行字符串与程序其他部分的缩进方式不一致。 799 | 800 | ``` python 801 | Yes: 802 | print ("This is much nicer.\n" 803 | "Do it this way.\n") 804 | ``` 805 | 806 | ``` python 807 | No: 808 | print """This is pretty ugly. 809 | Don't do this. 810 | """ 811 | ``` 812 | 813 | ### 2.10 TODO注释 814 | 815 | > **Tip** 816 | > 817 | > 为临时代码使用 TODO 注释,它是一种短期解决方案。不算完美,但够好了。 818 | 819 | TODO 注释应该在所有开头处包含 "TODO" 字符串,紧跟着是用括号括起来的你的名字,Email 地址或其它标识符。然后是一个可选的冒号。接着必须有一行注释,解释要做什么。主要目的是为了有一个统一的 TODO 格式,这样添加注释的人就可以搜索到(并可以按需提供更多细节)。写了 TODO 注释并不保证写的人会亲自解决问题。当你写了一个 TODO,请注上你的名字。 820 | 821 | ``` python 822 | # TODO(kl@gmail.com): Use a "*" here for string repetition. 823 | # TODO(Zeke) Change this to use relations. 824 | ``` 825 | 826 | 如果你的 TODO 是“将来做某事”的形式,那么请确保你包含了一个指定的日期(”2009年11月解决“)或者一个特定的事件(”等到所有的客户都可以处理 XML 请求时,就移除这些代码“)。 827 | 828 | ### 2.11 导入格式 829 | 830 | > **Tip** 831 | > 832 | > 每个导入应该独占一行。 833 | 834 | ``` python 835 | Yes: import os 836 | import sys 837 | ``` 838 | 839 | ``` python 840 | No: import os, sys 841 | ``` 842 | 843 | 导入总应该放在文件顶部,位于模块注释和文档字符串之后,模块全局变量和常量之前。导入应该按照从最通用到最不通用的顺序分组: 844 | 845 | 1. 标准库导入。 846 | 2. 第三方库导入。 847 | 3. 应用程序指定导入。 848 | 849 | 每种分组中,应该根据每个模块的完整包路径按字典序排序,忽略大小写。 850 | 851 | ``` python 852 | import foo 853 | from foo import bar 854 | from foo.bar import baz 855 | from foo.bar import Quux 856 | from Foob import ar 857 | ``` 858 | 859 | ### 2.12 语句 860 | ---- 861 | 862 | > **Tip** 863 | > 864 | > 通常每个语句应该独占一行。 865 | 866 | 不过,如果测试结果与测试语句在一行放得下,你也可以将它们放在同一行。如果是 `if` 语句,只有在没有 `else` 时才能这样做。特别地,绝不要对 `try/except` 这样做,因为 `try` 和 `except` 不能放在同一行。 867 | 868 | ``` python 869 | Yes: 870 | 871 | if foo: bar(foo) 872 | ``` 873 | 874 | ``` python 875 | No: 876 | 877 | if foo: bar(foo) 878 | else: baz(foo) 879 | 880 | try: bar(foo) 881 | except ValueError: baz(foo) 882 | 883 | try: 884 | bar(foo) 885 | except ValueError: baz(foo) 886 | ``` 887 | 888 | ### 2.13 访问控制 889 | 890 | > **Tip** 891 | > 892 | > 在 Python 中,对于琐碎又不太重要的访问函数,你应该直接使用公有变量来取代它们,这样可以避免额外的函数调用开销。 893 | 894 | 另一方面,如果访问更复杂,或者变量的访问开销很显著,那么你应该使用像 `get_foo()` 和 `set_foo()` 这样的函数调用。如果之前的代码行为允许通过属性访问,那么就不要将新的访问函数与属性绑定。这样,任何试图通过老方法访问变量的代码就没法运行,使用者也就会意识到复杂性发生了变化。 895 | 896 | ### 2.14 命名 897 | 898 | > **Tip** 899 | > 900 | > 按照以下的规范命名。 901 | 902 | **应该避免的名称** 903 | 904 | > - 单字符名称,除了计数器和迭代器。 905 | > - 包/模块名中的连字符。 906 | > - 双下划线开头并结尾的名称(Python 保留,例如 `__init__`)。 907 | 908 | **命名约定** 909 | 910 | > - 所谓”内部“表示仅模块内可用,或者在类内是保护或私有的。 911 | > - 用单下划线 `_` 开头表示模块变量或函数是 `protected` 的(使用` import * from` 时不会包含)。 912 | > - 用双下划线 `__` 开头的实例变量或方法表示类内私有。 913 | > - 将相关的类和顶级函数放在同一个模块里。不像 Java,没必要限制一个类一个模块。 914 | > - 对类名使用大写字母开头的单词(如 `CapWords`,即 Pascal 风格),但是模块名应该用小写加下划线的方式(如 `lower_with_under.py` )。尽管已经有很多现存的模块使用类似于 `CapWords.py` 这样的命名,但现在已经不鼓励这样做,因为如果模块名碰巧和类名一致,这会让人困扰。 915 | 916 | **Python之父Guido推荐的规范** 917 | 918 | | Type | Public | Internal | 919 | |----------------------------|----------------------|--------------------------------------------------------------------------| 920 | | Modules | lower\_with\_under | \_lower\_with\_under | 921 | | Packages | lower\_with\_under | | 922 | | Classes | CapWords | \_CapWords | 923 | | Exceptions | CapWords | | 924 | | Functions | lower\_with\_under() | \_lower\_with\_under() | 925 | | Global/Class Constants | CAPS\_WITH\_UNDER | \_CAPS\_WITH\_UNDER | 926 | | Global/Class Variables | lower\_with\_under | \_lower\_with\_under | 927 | | Instance Variables | lower\_with\_under | \_lower\_with\_under (protected) or \_\_lower\_with\_under (private) | 928 | | Method Names | lower\_with\_under() | \_lower\_with\_under() (protected) or \_\_lower\_with\_under() (private) | 929 | | Function/Method Parameters | lower\_with\_under | | 930 | | Local Variables | lower\_with\_under | | 931 | 932 | ### 2.15 Main 933 | 934 | > **Tip** 935 | > 936 | > 即使是一个打算被用作脚本的文件,也应该是可导入的。并且简单的导入不应该导致这个脚本的主功能被执行,这是一种副作用。主功能应该放在一个 `main()` 函数中。 937 | 938 | 在 Python 中,`pydoc` 以及单元测试要求模块必须是可导入的。你的代码应该在执行主程序前总是检查 `if __name__ == '__main__'`,这样当模块被导入时主程序就不会被执行。 939 | 940 | ``` python 941 | def main(): 942 | ... 943 | 944 | if __name__ == '__main__': 945 | main() 946 | ``` 947 | 948 | 所有的顶级代码在模块导入时都会被执行。要小心不要去调用函数,创建对象,或者执行那些不应该在使用 `pydoc` 时执行的操作。 949 | 950 | ## 3. 结语 951 | 952 | **请务必保持代码的一致性**。 953 | 954 | 如果你正在编辑代码,花几分钟看一下周边代码,然后决定风格。如果它们在所有的算术操作符两边都使用空格,那么你也应该这样做。如果它们的注释都用标记包围起来,那么你的注释也要这样。 955 | 956 | 制定风格指南的目的在于让代码有规可循,这样人们就可以专注于”你在说什么“,而不是”你在怎么说“。我们在这里给出的是全局的规范,但是本地的规范同样重要。如果你加到一个文件里的代码和原有代码大相径庭,它会让读者不知所措。避免这种情况。 957 | -------------------------------------------------------------------------------- /md/xm_developer_ros_styleguide.md: -------------------------------------------------------------------------------- 1 | # Team-Xmbot-Service-Robot ROS风格指南 2 | 3 | ## 0. 前言 4 | 5 | ### 0.1 版本 6 | 7 | - 1.0版本(2016.5.1):缪宇飏(myyerrol)创建团队 ROS 开发风格指南。本文档参考了 [ROS 官方风格指南](http://wiki.ros.org/DevelopersGuide),并根据实际的需求,对内容进行了适当的精简和改进。新队员应该认真学习本指南,掌握 ROS 基本的开发风格以及适用于本项目组的特定规则。如果有细节不统一的地方或者对本文档某处不是很认同,请在组内讨论统一之后,修改本指南。因为文档排版使用的是 Markdown 纯文本标记语言,也请后来者遵循本文档的开发方式,使用 Markdown 来修改、添加内容。 8 | 9 | ### 0.2 背景 10 | 11 | Team-Xmbot-Service-Robot(晓萌家庭服务机器人团队)作为开源项目,需要团队队员贡献代码,但是如果队员之间所使用的 ROS 开发风格不一致,便会给团队其他模块负责人造成不小的困扰。我们认为整洁、一致的 ROS 开发风格会使整个项目更加可管理和维护。因此,我们应当使用统一的 ROS 开发风格以使得每个功能包不仅能够在现在发挥作用,而且在将来的若干年之后其依旧能够存在、可以被复用、或者是能够被未来的新队员改进。 12 | 13 | ## 1. ROS 命名指南 14 | 15 | > **Tip** 16 | > 17 | > 所有命名的通用的标准是: 18 | > 19 | > - 命名要足够详细(不要吝啬长度),让开发者能通过名字知道你的意图。 20 | 21 | ### 1.1 Packages 22 | 23 | > **Tip** 24 | > 25 | > 包的命名要**全部小写**、使用统一的项目前缀,并且要足够详细。 26 | 27 | - 包的名字必须遵守 C++ 变量命名的规范:**全部小写**、以字母开始、单词之间使用下划线连接。比如:**xm_arm_moveit_config**。 28 | - 每个包的名字前都要有项目的根或子前缀。要求:如果包是作用于整个机器人或多个模块的,那么包的前缀应该为 **xm_**。比如:**xm_robot_hardware**(里面包括了底盘和机械臂两个模块部分)。如果包的作用域是针对于机器人的某一个模块的话,那包的前缀应是在原先根前缀 **xm_** 的基础上再添加相应模块的名字来构成。比如:**xm_arm_**、**xm_base_**、**xm_speech_**、**xm_vision_** 等。举个完整的包命名例子:**xm_arm_robot_hardware**(只与机械臂模块相关)。 29 | - BSR 项目请修改根前缀为 **xm_bsr_**,其他规则与上面一样。 30 | - 仿真项目请修改根前缀为 **xm_sim_**,其他规则与上面一样。 31 | - 包的名字应该足够的详细。比如:一个有关机械臂的运动规划包,那么 **xm_arm_motion_planning** 要比 **xm_arm_planning** 好一些。 32 | - 如果包的内容是基于其他第三方包的话,请在名字中进行指定。比如:一个基于 Moveit 的机械臂逆解算插件,就可以命名为 **xm_arm_moveit_ik_plugin**。 33 | - 元包(Metapackages)的命名比较特殊。因为元包包含了若干个子包,所以在给元包命名的时候,取一个能概括所有子包含义的名字是很有必要的。 34 | - 其他的请参照 [ROS Hector 团队](https://github.com/tu-darmstadt-ros-pkg)代码仓库中包的命名规范。 35 | 36 | ### 1.2 Messages 37 | 38 | > **Tip** 39 | > 40 | > 所有 `.msg`、`.srv`、`.action` 文件命名使用组内特定的规则。 41 | 42 | - 所有 `.msg`、`.srv`、`.action` 文件命名规则为:首先添加 **xm_** 前缀,之后添加每个文件具体的功能描述。描述部分必须使用驼峰命名法。例如:**xm_GripperCommand.action**。 43 | 44 | ### 1.3 Lanuch / Config / Xacro 45 | 46 | > **Tip** 47 | > 48 | > 所有机器人启动、配置、描述文件命名全部小写,并且描述的尽可能清楚。 49 | 50 | ### 1.4 Nodes 51 | 52 | > **Tip** 53 | > 54 | > 命名要小写,节点的类型(程序的名字)可以与包名中的某些单词重复,但一定要保证通过名字能准确知道其功能。 55 | 56 | 节点有类型和名字。节点类型指的是可执行程序的名字,而节点名字指的是启动后生成的,可以与其他节点通信的名字。 57 | 58 | - 节点名字与节点类型一样。要**全部小写**、使用下划线作为单词间的连接,并用有意义的单词来命名。 59 | - 通常,节点名字要与节点类型相同。但如果要启动多个同样类型的节点,请更改节点名字,使它们互不相同。 60 | - 通常,节点类型应该尽可能短,因为它们中的一部分已经被包的名字所包含了。比如:假设存在一个包 **laser_scan**,其中包含观察激光数据的程序,那程序的名字应该命名为 **viewer**,而不是 **laser_scan_viewer**。但是本人不太认同以上官方的说法,因为这与 C++ 代码命名指南相冲突(代码本身要命名的足够详细)。这条规则可以成立的条件是要把代码名和包名联系来看,但如果只看代码名字的话,我想很难从有限的单词中可以看出代码所要实现的功能。因此,**这条规则可以忽略掉。只要代码的名字好理解,与包名某些单词重复也是没有关系的**。比如: 61 | 62 | ``` bash 63 | $ rosrun xm_arm_teleop xm_arm_teleop_joint_position_keyboard 64 | ``` 65 | 66 | ### 1.5 Topic / Service / Action 67 | 68 | > **Tip** 69 | > 70 | > 命名小写、具有描述性、且添加适合的前缀来明确名字的作用范围。 71 | 72 | topic、service 和 action 的名字是节点服务端、客户端之间通信的桥梁。它们存在于一个分层的命名空间中,客户端便可以提供机制在运行时来重映射它们的名字。因此,它们相较于包的命名更加灵活。 73 | 74 | - topic、service 和 action 的名字遵循 C++ 变量命名指南:小写、下划线。 75 | - 命名应该足够的具有描述性。并且最好添加相应的前缀来指明 topic、service、action 的作用范围。比如:假设机械臂关节节点要通过 topic 发布关节状态数据,那命名成 **/joint_states** 要比 **/states** 好。**/xm_arm/joint_states** 又要比 **/joint_states** 好。 76 | - 如果某些程序发布的 topic、service、action 名字没有使用前缀,请在 launch 文件中使用 ``来重映射。比如:节点 robot_state_publisher 默认订阅的 topic 是 **/joint_states**,使用重映射可以使其订阅 **/xm_arm/joint_states** 上的数据。 77 | 78 | ## 2. ROS 格式指南 79 | 80 | > **Tip** 81 | > 82 | > 所有格式的通用标准是: 83 | > 84 | > - 在 ROS 包中,除代码外,其他跟 ROS 有关的文件都需要使用官方指定的 2 格缩进。 85 | > - 所有文件的末尾都要留出一个空行,不能多也不能少。 86 | 87 | ### 2.1 Package.xml 88 | 89 | > **Tip** 90 | > 91 | > 使用项目组内特定的风格。 92 | 93 | package.xml 是每个 ROS 包都必须包含的,可以通过使用 `catkin_create_pkg` 自动生成,其他详细介绍请看 [ROS package](http://wiki.ros.org/catkin/package.xml)。以下是 package.xml 的格式风格。 94 | 95 | - `version`版本标签的含义为:主版本-子版本-修改次数。现在所有包的主版本默认为 1 ,子版本和修改次数默认为 0 。即初始化为 `1.0.0`。之后,主、子版本和修改次数的值会伴随 ROS 包的修改而不断变化。当修改次数达到一定值时,可以将子版本加 1,而修改次数重新归零。以此类推,如果子版本数增到一定程度时,就可以将主版本加 1,而其他两个归零。对于修改到什么程度就可以向子版本或主版本进 1,请各模块负责人自己决定。举个例子:`1.0.0` -> `1.0.20` -> `1.1.0` -> `1.2.10` -> `2.0.0`。 96 | - 内容全部使用 2 格缩进。 97 | - 所有在 package.xml 中被注释的都要删除掉,只留下最后精简过的、有用的信息。 98 | - `` 和 `` 的区别在于,`` 是包的主要或最早开发者,而 `` 则是之后为包提供修改的开发者。显然,出现在 `` 的名字也应该出现在 `` 中。如果有其他开发者修补了 Bug 或进行了增量式的开发,请将名字写在 `` 标签之后。 99 | - 因为 ROS 使用的软件许可证协议是 BSD,所以我们所有的包也要以 BSD 协议发布出去。 100 | - 空格和空行的使用请严格遵循下面完整例子所实现的那样。 101 | - 如果包是元包(Metapackages),请在 package.xml 的末尾处添加以下内容: 102 | 103 | ```xml 104 | 105 | 106 | 107 | ``` 108 | 109 | 以下是个完整的例子: 110 | 111 | ``` xml 112 | 113 | 114 | xm_arm_robot_hardware 115 | 0.0.1 116 | The xm_arm_robot_hardware package implements hardware interface by using ros_control. 117 | 118 | myyerrol 119 | 120 | BSD 121 | 122 | myyerrol 123 | 124 | catkin 125 | 126 | control_toolbox 127 | controller_manager 128 | hardware_interface 129 | realtime_tools 130 | roscpp 131 | sensor_msgs 132 | std_msgs 133 | 134 | control_toolbox 135 | controller_manager 136 | hardware_interface 137 | realtime_tools 138 | roscpp 139 | sensor_msgs 140 | std_msgs 141 | 142 | 143 | 144 | ``` 145 | 146 | ### 2.2 CMakeLists.txt 147 | 148 | > **Tip** 149 | > 150 | > 使用项目组内特定的风格。 151 | 152 | CMakeLists.txt 是 CMake 编译系统的输入文件,用来描述如何构建代码。更多详细的介绍请看 [ROS CMakeLists.txt](http://wiki.ros.org/catkin/CMakeLists.txt)。以下简单地介绍编写或修改 CMakeLists.txt 所使用的格式风格。 153 | 154 | - 内容全部使用 2 格缩进。 155 | - 每个命令之间要空 1 行。 156 | - 所有在 CMakeLists.txt 中被注释的都要删除掉,只留下最后精简过的、有用的信息。 157 | - 每个命令中的元素最好每行只写一个,而且要对齐。这样看起来更清楚。 158 | - 针对于元包(MetaPackages),请在 CMakeLists 中写入以下内容: 159 | 160 | ``` cmake 161 | cmake_minimum_required(VERSION 2.8.3) 162 | project() 163 | find_package(catkin REQUIRED) 164 | catkin_metapackage() 165 | ``` 166 | 167 | 以下是完整的例子: 168 | 169 | ``` cmake 170 | cmake_minimum_required(VERSION 2.8.3) 171 | project(xm_arm_robot_hardware) 172 | 173 | find_package(catkin REQUIRED COMPONENTS 174 | control_toolbox 175 | controller_manager 176 | hardware_interface 177 | realtime_tools 178 | roscpp 179 | sensor_msgs 180 | std_msgs 181 | ) 182 | 183 | catkin_package() 184 | 185 | include_directories( 186 | include 187 | ${catkin_INCLUDE_DIRS} 188 | ) 189 | 190 | add_executable( 191 | xm_arm_robot_hardware 192 | include/xm_arm_robot_hardware/xm_arm_robot_hardware.h 193 | src/xm_arm_robot_hardware.cpp 194 | src/main.cpp 195 | ) 196 | 197 | target_link_libraries( 198 | xm_arm_robot_hardware 199 | ${catkin_LIBRARIES} 200 | ) 201 | 202 | ``` 203 | 204 | ### 2.3 Launch 205 | 206 | > **Tip** 207 | > 208 | > 使用项目组内特定的风格。 209 | 210 | launch 文件作为 roslaunch 命令的输入,可以启动多个 ROS 节点并在参数服务器上设置参数。更多有关详细内容请关注 [ROS launch](http://wiki.ros.org/roslaunch/XML)。以下简介 launch 文件的格式风格。 211 | 212 | - 所有内容 2 格缩进。 213 | - 每个标签之间要空 1 行。如果是相同标签,则之间可以不空行。 214 | - 每个标签的开头,最好能有一行注释来解释标签所要实现的功能。 215 | - 每个标签的结束内容要与 `/>` 之间空一格。 216 | - 每个标签内部的 `=` 前后是没有空格的。 217 | - 针对根标签内部可以嵌入子标签的情况,请子标签缩进 2 个空格。 218 | - 如果标签一行太长,则请适当的换行。换行之后请与上一行的元素对齐。 219 | - `` 的基本声明顺序为:`name`, `pkg`, `type`。 220 | 221 | ``` xml 222 | 223 | ``` 224 | 225 | - `` 的基本声明顺序为:`name`,`value`。 226 | 227 | ``` xml 228 | 229 | ``` 230 | 231 | - `` 的基本声明顺序为:`name`, `type`, `value`。 232 | 233 | ``` xml 234 | 235 | ``` 236 | 237 | - `` 的基本声明顺序为:`command`,`file`。 238 | 239 | ``` xml 240 | 241 | ``` 242 | 243 | 以下是完整的例子: 244 | 245 | ``` xml 246 | 247 | 248 | 249 | 250 | 251 | 254 | 255 | 256 | 258 | 259 | 260 | 261 | 262 | 263 | ``` 264 | 265 | ### 2.4 License 266 | 267 | > **Tip** 268 | > 269 | > 使用基于 BSD 的项目组软件开发许可证协议。 270 | 271 | 因为,ROS 开发使用的是 BSD 软件开发许可证协议。所以我们项目组在发布代码和软件包的时候,也要使用 BSD 许可证协议。以下简介格式指南。 272 | 273 | - 每个 ROS 软件包的目录下都要放置一个名为 LICENSE 的许可证文件。 274 | - 每个 C++ 代码的头文件和源文件都要在开始放置许可证协议。 275 | - 每个 Python 代码的头部都要放置许可证协议。 276 | 277 | 以下是相应的例子: 278 | 279 | ``` plain 280 | Software License Agreement (BSD License) 281 | 282 | Copyright (c) 2016, Team-Xmbot-Service-Robot 283 | All rights reserved. 284 | 285 | Redistribution and use in source and binary forms, with or without 286 | modification, are permitted provided that the following conditions 287 | are met: 288 | 289 | * Redistributions of source code must retain the above copyright 290 | notice, this list of conditions and the following disclaimer. 291 | * Redistributions in binary form must reproduce the above 292 | copyright notice, this list of conditions and the following 293 | disclaimer in the documentation and/or other materials provided 294 | with the distribution. 295 | * Neither the name of the Team-Xmbot-Service-Robot nor the names 296 | of its contributors may be used to endorse or promote products 297 | derived from this software without specific prior written 298 | permission. 299 | 300 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 301 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 302 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 303 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 304 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 305 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 306 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 307 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 308 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 309 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 310 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 311 | POSSIBILITY OF SUCH DAMAGE. 312 | 313 | ``` 314 | 315 | ``` cpp 316 | /********************************************************************* 317 | * Software License Agreement (BSD License) 318 | * 319 | * Copyright (c) 2016, Team-Xmbot-Service-Robot 320 | * All rights reserved. 321 | * 322 | * Redistribution and use in source and binary forms, with or without 323 | * modification, are permitted provided that the following conditions 324 | * are met: 325 | * 326 | * * Redistributions of source code must retain the above copyright 327 | * notice, this list of conditions and the following disclaimer. 328 | * * Redistributions in binary form must reproduce the above 329 | * copyright notice, this list of conditions and the following 330 | * disclaimer in the documentation and/or other materials provided 331 | * with the distribution. 332 | * * Neither the name of the Team-Xmbot-Service-Robot nor the names 333 | * of its contributors may be used to endorse or promote products 334 | * derived from this software without specific prior written 335 | * permission. 336 | * 337 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 338 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 339 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 340 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 341 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 342 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 343 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 344 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 345 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 346 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 347 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 348 | * POSSIBILITY OF SUCH DAMAGE. 349 | ********************************************************************/ 350 | 351 | ``` 352 | 353 | ``` python 354 | """ 355 | ******************************************************************** 356 | * Software License Agreement (BSD License) 357 | * 358 | * Copyright (c) 2016, Team-Xmbot-Service-Robot 359 | * All rights reserved. 360 | * 361 | * Redistribution and use in source and binary forms, with or without 362 | * modification, are permitted provided that the following conditions 363 | * are met: 364 | * 365 | * * Redistributions of source code must retain the above copyright 366 | * notice, this list of conditions and the following disclaimer. 367 | * * Redistributions in binary form must reproduce the above 368 | * copyright notice, this list of conditions and the following 369 | * disclaimer in the documentation and/or other materials provided 370 | * with the distribution. 371 | * * Neither the name of the Team-Xmbot-Service-Robot nor the names 372 | * of its contributors may be used to endorse or promote products 373 | * derived from this software without specific prior written 374 | * permission. 375 | * 376 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 377 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 378 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 379 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 380 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 381 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 382 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 383 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 384 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 385 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 386 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 387 | * POSSIBILITY OF SUCH DAMAGE. 388 | ******************************************************************** 389 | """ 390 | ``` 391 | 392 | ## 3. ROS 管理指南 393 | 394 | ### 3.1 目录位置 395 | 396 | > **Tip** 397 | > 398 | > 删除没用的空目录。其他文件的放置位置要合理。 399 | 400 | 以下路径都是相对于 ROS 包 `` 根目录的。 401 | 402 | - C++ 代码头文件要放到 `include//` 目录下。 403 | - C++ 源文件放到 `src/` 目录下。 404 | - Python 文件要放到 `scripts/` 目录下。 405 | - Python 生成的模块要放到 `src//` 目录下。 406 | - lanucn 文件要放到 `launch/` 目录下。 407 | - rviz 可视化配置文件要放到 `rviz/` 目录下。 408 | - urdf 机器人描述文件要放到 `urdf/` 目录下。 409 | - 机器人零件模型要放到 `meshes/` 目录下。 410 | - gazebo 仿真环境配置文件要放到 `world/` 目录下。 411 | - message 文件要放到 `msg/` 目录下。 412 | - service 文件要放到 `srv/` 目录下。 413 | - action 文件要放到 `action/` 目录下。 414 | - 其他配置文件要放到 `config/` 目录下。 415 | 416 | ### 3.2 节点通信类型 417 | 418 | > **Tip** 419 | > 420 | > 根据模块之间通信需求,合理使用。 421 | 422 | 423 | ### 3.3 元包 424 | 425 | > **Tip** 426 | > 427 | > 可以将功能相近的若干个包组织在一个元包内。 428 | -------------------------------------------------------------------------------- /pdf/xm_developer_cpp_styleguide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/pdf/xm_developer_cpp_styleguide.pdf -------------------------------------------------------------------------------- /pdf/xm_developer_github_styleguide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/pdf/xm_developer_github_styleguide.pdf -------------------------------------------------------------------------------- /pdf/xm_developer_python_styleguide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/pdf/xm_developer_python_styleguide.pdf -------------------------------------------------------------------------------- /pdf/xm_developer_ros_styleguide.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xm-project/xm_developer_guide/c0b425f394f666ad2c9efb322e05e441575ef292/pdf/xm_developer_ros_styleguide.pdf --------------------------------------------------------------------------------