软件设计原则
本文转载自blinkfox的博客,总结了常用的软件设计原则,对于工程上来说很有指导意义。
一、前言
软件也像人一样,具有生命力,从出生到死亡,会经历多种变化。软件架构设计也不是一蹴而就的,是不断地演进发展。每个程序员都可以从理解编程原则和模式中受益。
软件设计原则是一组帮助我们避开不良设计的指导方针。根据Robert Martin的理论,应该避免不良设计的以下三个重要特点:
僵化:很难做改动,因为每一个细微的改动都会影响到系统大量的其他功能
脆弱:每当你做一次改动,总会引起系统中预期之外的部分出现故障
死板:代码很难在其他应用中重用,因其不能从当前应用中单独抽离出来
下面这些软件设计原则是我从一些书籍和网络中收集而来,并不完整,而且你也需要在一些有“冲突的原则”之间进行权衡和取舍。本文或许会对你的编程、程序设计、讨论或评审工作有所帮助。
二、通用设计原则
1. KISS
所谓KISS原则,即:Keep It Simple,Stupid,指设计时要坚持简约原则,避免不必要的复杂化,并且易于修改。
Everything should be made as simple as p ...
跟随一条指令来看LLVM的基本结构
LLVM是一个很复杂的软件,了解LLVM的工作原理不是很容易,然而,对于刚开始接触LLVM整个框架的工作原理来说,详细而深入,不如广泛而浅显,所以有了这一篇文章。
通过跟随一条指令在LLVM中的各个passes中的状态变化,从源程序开始,到目标代码结束,可以让我们对LLVM的整体框架有个大致的认识。
这篇文章基于Life of an instruction in LLVM,文章大部分内容与参考文章一致,但由于参考文章编辑于2012年11月,当时的LLVM版本是3.2,距现在新的LLVM版本已有一些差异,所以有部分内容我做了调整。
这篇文章不会详细讲解各个passes中的实现,尽量易于理解,尽量紧贴指令的变化过程。
有关于LLVM中的一些基本概念,可以参考:https://blog.csdn.net/SiberiaBear/article/details/103111028。
输入代码
使用的输入代码与参考文章一致,选择一段C语言来开始:
int foo(int aa, int bb, int cc) {int sum = aa + bb;return sum / cc; ...
LeetCode-307. 区域和检索 - 数组可修改
给你一个数组 nums ,请你完成两类查询。
其中一类查询要求 更新 数组 nums 下标对应的值
另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 和 ,其中 left <= right
实现 NumArray 类:
NumArray(int[] nums) 用整数数组 nums 初始化对象
void update(int index, int val) 将 nums[index] 的值 更新 为 val
int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的 和 (即,nums[left] + nums[left + 1], ..., nums[right])
示例 1:
输入:["NumArray", "sumRange", "update", "sumRange"][[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]输出:[null, 9, null, 8]解释:NumArray nu ...
自定义LLVM中的PASS
所有的LLVM pass都是Pass类的子类,他们都继承并重写了Pass类中所定义的虚函数。根据需求,我们可以选择继承ModulePass ,CallGraphSCCPass,FunctionPass 或者LoopPass或者RegionPass 等类。通过继承这些父类,我们可以告诉编译系统我们的自定义pass大概要做哪些事情,并且如何去与其他的pass进行组合。
接下来我们尝试写一个简单的pass,从编码,到编译,再到运行,最后讨论如何编写一个高级的pass。
环境设置
下面我们编写一个Hello World pass。我们的pass将实现一个非常简单的功能--打印函数名,这意味着它不会修改源程序,只是简单地打印源程序的相关信息。
首先,配置并构建LLVM项目,然后在LLVM源码文件夹下新建一个目录。遵照官方文档,我们在llvm源码树的lib/Transforms目录下新建一个example目录。
然后将下面的代码添加到lib/Transforms/CMakeLists.txt中
add_subdirectory(example)
这样CMake会将example.cpp文件链接到 ...
LLVM IR总览
LLVM IR(Intermediate Representation)是一种中间语言表示,作为编译器前端和后端的分水岭。LLVM 编译器的前端——Clang 负责产生 IR,而其后端负责消费 IR。
编译器 IR 的设计体现了权衡的计算思维。低级的 IR(即更接近目标代码的 IR)允许编译器更容易地生成针对特定硬件的优化代码,但不利于支持多目标代码的生成。高级的 IR 允许优化器更容易地提取源代码的意图,但不利于编译器根据不同的硬件特性进行代码优化。
LLVM IR 的设计采用common IR和specific IR相结合的方式。common IR旨在不同的后端共享对源程序的相同理解,以将其转换为不同的目标代码。除此之外,也为多个后端之间共享一组与目标无关的优化提供了可能性。specific IR允许不同的后端在不同的较低级别优化目标代码。这样做,既可以支持多目标代码的生成,也兼顾了目标代码的执行效率。
LLVM IR 有如下 3 种等价形式:
内存表示
类llvm::Function、llvm::Instruction等用于表示common IR。
类llvm::Machin ...
LeetCode-7. 整数反转
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
输入:x = 123输出:321
示例 2:
输入:x = -123输出:-321
示例 3:
输入:x = 120输出:21
示例 4:
输入:x = 0输出:0
提示:
-231 <= x <= 231 - 1
思路
先不考虑溢出的情况,基本思路是,只需每次取出x的个位,将其添加到翻转数字的个位上即可。
class Solution {public: int reverse(int x) { int res = 0; while (x != 0) { // 将x的个位添加到result的个位上 res *= 10; res += x % 10; x /= 10; } return r ...
LeetCode-17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
img
示例 1:
输入:digits = "23"输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""输出:[]
示例 3:
输入:digits = "2"输出:["a","b","c"]
提示:
0 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字。
思路
一道常规的dfs题目。使用Java,StringBuffer类可以很好地插入、删除和构建字符串。
class Solution { public Map<Integer, String[]> charMap; public List<String> ans; public String digits; public List<String& ...
RAID:原理与计算
独立硬盘冗余阵列(RAID, Redundant Array of Independent Disks),旧称廉价磁盘冗余阵列(Redundant Array of Inexpensive Disks),简称磁盘阵列。利用虚拟化存储技术把多个硬盘组合起来,成为一个或多个硬盘阵列组,目的为提升性能或减少冗余,或是两者同时提升。
RAID技术使用多个磁盘构建了一个更快、更大、更可靠的磁盘系统。在外部看来,RAID就是一个磁盘,而在内部,RAID所组成的系统则十分复杂,由多个磁盘、内存(包括易失性和非易失性内存)以及一个或多个处理器来管理。
与使用单个磁盘相比,RAID具有以下优点:
性能更好。通过并行使用多个磁盘,可以大大加快IO时间。
可靠性更好。RAID通过某种形式的数据冗余,可以最大程度减轻数据错误的情况。
容量更大。显然,采用多个磁盘使得RAID的容量更大了。
本文我们主要探讨一些主要的RAID技术原理,并通过实际计算加深对RAID的理解。
数据分条
我们想要通过并行访问硬盘来提高数据的访问速度,就必须将数据分散到各个磁盘中。而拆分数据最简单的形式就是数据分条(data st ...
快速掌握CMake「四、关键概念」
「CMake」是一个跨平台的C++项目管理解决方案,它可以帮助用户方便地管理大型C++项目,支持生成不同平台的Makefile。本系列文章可以帮助你快速掌握CMake,所有内容来源于Mastering Cmake。
本篇文章为该系列的第四篇。我们主要介绍CMake的一些关键概念。
学习CMake的过程中,你可能已经遇到了很多不同的概念,比如目标(targets),生成器(generators)和命令(commands)。理解这些概念可以让你高效地编写CMakeLists文件。许多CMake中的对象比如目标,目录和源文件都拥有属性。属性是一个特定对象所拥有的的键值对。访问属性最通用的方法是通过set_property和get_property命令。这些命令可以让你设置和获取一个CMake对象的属性。你可以查看cmake-properties手册,其中列出了所有支持的属性。在命令行中,你可以运行对CMake命令附加--help-property-list选项来查看所有CMake支持的属性。
目标
CMake中最重要的概念应该是「目标」。目标代表着可执行文件,库,以及由CMake构建的工具 ...
快速掌握CMake「三、CMake缓存」
「CMake」是一个跨平台的C++项目管理解决方案,它可以帮助用户方便地管理大型C++项目,支持生成不同平台的Makefile。本系列文章可以帮助你快速掌握CMake,所有内容来源于Mastering Cmake。
本篇文章为该系列的第三篇。你可以将CMake缓存理解为一个配置文件。当你在项目中第一次运行CMake时,会在build目录的顶层生成CMakeCache.txt文件,CMake使用这个文件来存储一系列全局缓存变量。
缓存文件有几个用途。首先是存储用户的设定,这样当再次运行CMake时,他们就不需要重新输入各种选项。例如,option命令创建一个布尔变量并将其存储在缓存中。
option(USE_JPEG "Do you want to use the jpeg library")
这行代码会创建一个名为USE_JPEG的遍历,并将其放入缓存中。这样,用户就可以在用户界面中设置这个变量,它的值将保留下来,以便用户将来再次运行CMake。要在缓存中创建一个变量,可以使用类似option,find_file命令,或者可以使用标准的set命令,但要加上CACH ...