开发历程
开发历程
每开发一个特定的功能时,新建一个
develop-function
分支,等到这个部分开发完了之后,merge
到主分支中。单元测试可以被视为一种问题记录和行为文档,每当开发过程中程序出现错误或者不期望的行为时,编写一个单元测试来测试和记录这个问题,可以更清楚地记录这个程序可能会出现什么问题,并确保这个特定问题再未来不会再重新出现。测试也是代码文档的一个补充。
目前所采用的组织方案是在一个.py文件里实现一个特定模块的函数和类,为了便于后续采用C/C++对Python的代码进行优化,把所有的函数都拿到类的外面,定义到.py文件中,函数本身不和类做任何交互,输入是numpy数组,输出也是numpy数组,比如
calculate_normal(array)
。当比如surface
类需要计算法向量时,另外建一个函数去包装调用全局函数。实际上类的static
装饰器起到的功能和这个差不多,只起到更改函数命名空间的作用。但是当用多个.py文件构建多个模块时,命名空间实际上已经改变了,不再需要static
装饰器了。为了更清楚地弄清楚程序的运行流程,以及后续的可能用静态优化,所有的参数、属性、方法都采用类型注解。
现在是写功能直接在.py文件里去写,大块的功能和逻辑直接用ChatGPT或者Copilot自动生成,同时让他们为我的函数添加文档字符串。然后用.ipynb去做实验,把.ipynb用.gitignore忽略掉,调试成功了以后把.ipynb中的实验整理到tests下面的单元测试里。这样的Python开发是合理的吗?不过至少比我最开始的时候规范了许多..
可以用类方法来实现Python的构造函数重载。
比如定义一个类方法
1
2
3
4
5
6
7def __init__(self, surfaces):
self.surfaces = surfaces
def from_points(cls, points):
surfaces = from_points_to_surfaces(points)
return cls(surfaces)可以通过方法重写,先实现较简单的情况,比如Geometry可能代表着任意类型的三维结构,写它的通用的内部检测函数会比较复杂也比较耗时,但对于长方体来说判断一个点是不是在它的内部就很容易。在大部分情况下都是处理长方体的情况下,有必要重写检测函数。这时候在Geometry的类中可以先把这个方法写进去,但是不实现。为了逻辑上不出现错误,可以在函数内部抛出错误:
1
rNotImplementedError
rebase可以处理新分支在开发过程中遇到了Bug,然后在出现Bug的特性分支上修复错误,主分支用cherry-pick了fix提交,之后新分支想装在修复bug后的主分支上工作,无缝融合地提交新功能,好像新分支是在修复bug的主分支的节点引出来工作的。这时可以
rebase main
。为什么本地删除分支和远程删除分支的命令不同呢?
本地删除分支是
1
git branch -d branch_name
这只是在本地Git仓库中删除一个指针,如果分支被删除后,那些提交不再被任何其他的分支或标签引用,它们将变成“悬空”的,Git 有一个垃圾收集机制 (
git gc
) 会在某个时间点清理这些不再被任何分支或标签引用的提交,但这通常不会立刻发生。而远程删除分支,本地做的操作实际上并不是直接删除远程分支,而是发送一个请求到远程仓库,要求它删除一个分支。因为我们是在与一个远程服务器通信,所以需要使用
git push
命令来发送这个请求。1
git push --delete remote_name branch_name
为什么本地新建了一个分支直接运行
git push
会报错,这是因为新建的分支还没有一个默认的远程仓库,因此我们要通过设置upstream的方式来告诉这个分支如何与远程的某个分支相关联。1
git push --set-upstream origin develop-cython
通过
push
操作可以把这个新分支推送远程仓库,远程仓库会创建一个对应的新分支。UML的设计者和推动者之一Grady Booch说到:
在UML出现之前和之后,软件项目成功的关键依然是 - 智慧地使用技术、遵从一个好的软件开发过程、有经验的开发者和适当的技能组合。
能够对一个问题建立模型的确非常好,但是我们不要忘记软件开发的目的是要通过写代码解决用户的问题。软件工程方法论专家Hans-Peter Hoffmann有过这样的经历:
当时他接到英国的求助电话。客户说,他们建立了一个模型,这个模型得到了客户、管理人员和开发者的共同认可。但问题是,有了这个模型,他们却不知道下一步该做什么!
当在一个功能上开发一半,我感到厌烦了,想要同步开发些新的功能,比如维护维护文档一类的,我想要回到
main
分支,然后再建立一个develop-docs
分支,这时候我该怎么做呢?我可以用
git stash
命令临时保存这些更改到当前分支,然后回到主分支继续做这些操作就可以了。如果没有执行
git stash
,如果当前分支更改的文件在主分支没有冲突,那么更改的文件会跟随我到达主分支上。如果主分支的文件和分支文件有冲突,Git将不允许切换分支。同时,如果你在主分支上做了一些修改但还没有提交,当你创建一个新的分支时,这个新的分支会从最后一个提交的状态(即当前分支的HEAD指向的提交)开始,不会包含你的未提交修改。但是,这些未提交的修改仍然“存在”在你的工作区中,这意味着当你切换到新创建的分支时,这些未提交的更改将“跟随”你到新的分支。在新的分支上你可以选择提交这些更改或不提交。
git stash
默认只储存暂存区中的文件,把暂存区的内容移动到存储区。然后主分支更新完了,这时候再回到过去的分支,可以执行
git rebase main
,git
会将当前所在的分支重置到main分支的最新提交,Git首先找到当前分支和main分支的共同祖先,然后将当前分支自那时以来的所做的所有提交移动到一个临时区域。接下来,Git将当前分支重置到main分支的最新提交,最后,Git会重新应用临时区域中的每一个提交到当前的分支上。更新完了,再git stash pop
恢复暂存区的更改。关于版本号命名,根据语义版本控制规范,一个版本号被分为三部分:
MAJOR.MINOR.PATCH
,他们的含义如下:- MAJOR: 大版本号,当你做了不兼容的API更改时,你需要增加这个数字。
- MINOR: 小版本号,当你添加了向后兼容的新功能时,你需要增加这个数字。
- PATCH: 补丁号,当你做了向后兼容的问题修复时,你需要增加这个数字。
例如:
- 1.0.0: 初始的稳定版本
- 1.1.0: 添加了新的向后兼容的功能
- 1.1.1: 修复了一个向后兼容的bug
确保每个
commit
都能让程序正确运行是一个很好的实践,这可以让我们轻松回滚到任何点,不用担心这个点的程序会发生崩溃,未来程序出现问题,也非常容易找到和修复错误。同时,这样也保证了大家可以更加自信地基于现有的提交工作,整个提交记录也会更加干净、清晰和简洁。当然,这不意味着每个提交都必须是一个完全完成的功能或者完全修复的错误,它仅仅意味着每个提交不应该“打破”现有的功能或引入新的错误。我们可以通过多次提交来逐步构建一个新功能或修复一个错误,只要每个提交都保持代码库的稳定和可用,这就足够了。所以单元测试和集成测试在这里很重要,如果单元测试没有更新,程序就会出问题。
我们应该避免不必要的面向对象编程。我们应该尽可能少地使用具有隐式上下文和副作用的函数和程序。函数的隐式上下文比如全局变量,副作用是指函数对其隐式上下文所做的更改。如果函数会保存或删除全局变量中的数据,则称它具有副作用。将有上下文和副作用的函数与纯函数隔离开来,可以获得以下好处:
- 纯函数是确定的:给定一个固定的输入,输出始终是相同的。
- 纯函数需要重构或优化时,更容易替换或更改。
- 纯函数更易于使用单元测试进行测试:对于复杂的上下文设置和事后的数据清理的需求更少。
- 纯函数更易于操作、修饰和传递。
Git的冲突指的只是更改的冲突,而不是文件的冲突。在Git中,冲突分为以下几种情况:
- 编辑冲突:这是最常见的冲突类型,当两个不同的分支修改了同一文件中的同一行时就会发生。这种情况需要手动解决冲突,通常通过合并工具或者编辑器来解决。
- 文件冲突:这种冲突是由于文件级的更改引起的,例如,一个分支中删除了一个文件,而另一个分支中修改了该文件。
- 重命名冲突:当一个分支中重命名了一个文件,而另一个分支中修改了原文件名的文件时,就会发生重命名冲突。
folk之后pull request也没什么不同,就相当于多了几个其他人的分支,和自己在main分支合并某一个特性分支时发生的事情是一样的。但是为了让主分支干净一些并且减少冲突发生的可能性,让其他人在合并到主分支之前,rebase最新的主分支是一个好的主义。也就是说使用Pull Request本质上也是进行主分支和某个特性分支的合并,只不过这时候特性分支是另一个人的了。
remote的分支和本地的分支都是一样的东西,比如在执行merge操作等的时候,本地的remote分支实际上就相当于远程实际分支的镜像。只不过权限不同,本地的远程跟踪分支是只读的,你不能直接在这些分支上做任何更改或执行合并。它们的目的是作为远程仓库的参考和镜像,让你能够查看远程仓库的状态和历史。但是,你可以将其合并到你的本地分支中。fetch就是拉取最新的远程分支的更改,更新本地的远程分支镜像,自己之后可以决定是否merge远程分支的更改。git pull就是先更新本地远程分支的镜像,然后直接merge到本地分支上。所以也就是说如果发生了冲突并解决了冲突,本地会多一次合并提交,之后更改再push到远程分支的时候,这个合并提交也会体现在提交历史里。当然,一个人的时候或者两个人分离比较清楚的时候,这时候Git默认会进行快进合并,只是简单地将HEAD移动到另一个分支的提交,而不会创建任何新的合并提交。
.gitignore
文件默认是应用于整个工作区的,而不是根目录,因为我忽略了所有的*.png
,这导致项目文档中的png也被忽略了。如果只想忽略项目根目录中的某个文件,需要/*.png
。同时,如果已经被Git跟踪的文件,添加到.gitignore
中是没有作用的。这时候,可以用git rm --cached
命令将文件从Git索引中删除。git rm
是用于从Git索引和工作目录中同时删除文件,而--cached
是告诉Git只从索引中删除文件,而不是从工作目录中删除文件。这意味着文件在本地文件系统中仍然存在,但Git不再跟踪它。更改某个目录的大小写,让我硬重置了几次。在Git中,文件名的大小写敏感性是由
core.ignoreCase
配置选项控制的。在大多数Unix-like系统中,文件系统是区分大小写的,而在Windows和macOS的某些文件系统中则不是。要使Git区分文件名的大小写,你需要将core.ignoreCase
配置选项设置为false
。你可以使用以下命令来做到这一点:1
git config core.ignoreCase false
但是这将只影响新检出的和未来的工作;它不会重新处理已存在与repository中的文件对象。这个时候,可以通过上面的命令,
1
git rm --cached -r OLD_dir
删除旧目录在Git中的索引。
依赖倒置:抽象不依赖于细节,细节应该依赖于抽象;高层模块不应该依赖于底层模块,两者都应该依赖其抽象。面向接口编程。所以抽象基类是一个好主意,完全通过抽象把整套流程搭起来,然后具体实现类按照抽象基类的接口实现自己的代码。
结合蒙特卡罗平台开发的一些思考,整体框架是Python搭的,部分耗时的函数我用Cython重写,而为了方便单独拆出来某个函数重写,程序就必须满足单一职责原则。同时,为了确保各个模块间的弱耦合以及便于多态开发,程序实现时候需要满足依赖倒置原则,抽象不依赖于细节,而是细节依赖于抽象。通过各个抽象接口把各个模块链接起来,可以开发各个抽象类的具体实现子类。另一方面,当我想扩展我的程序的时候,我不应该修改原有的程序,导致所有的测试模块和案例都需要重写,而是应该满足开闭原则,让系统对扩展开放,对修改封闭,增加新功能不应修改现有代码。虽然不满足这些原则也能实现一些想法,但是满足了这些原则后进行的开发和更改,会让系统更加稳定和可靠,易于后续的开发。
- 单一职责原则 (SRP)
- 原理:每个模块或函数应该只负责一个功能或职责。
- 应用:你将耗时的函数单独拆出来,确保每个函数只完成一个任务,这使得使用Cython进行单独优化变得简单和可行。
- 开闭原则 (OCP)
- 原理:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。
- 应用:当需要添加新功能时,你通过添加新的代码而不是修改现有代码来实现,这样保持了现有代码的稳定性并降低了错误引入的可能性。
- 依赖倒置原则 (DIP)
- 原理:高层模块不应该依赖于低层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。
- 应用:你通过创建抽象接口来链接各个模块,这允许你轻松开发各个抽象类的具体实现子类,确保了模块间的低耦合和高内聚,使得系统更加灵活和可扩展。
比如考虑这样一个情况,现在我的一个长方体有6个面的对象,每一步要进行碰撞检测和哪个面相交,假如我的某一个面通过一个纳米桥与另一个结构相连,这个接触面是某个表面我的表面的子集。当几何体的Surface和几何体弱耦合的时候,我只需要在几何体维护的列表中加上这个小子面就可以了。在碰撞检测时,优先检测这个子面,而其他所有的部分都不受到影响,整个仿真的逻辑和功能不需要做出任何更改。这和之前加多个热源的想法一样,像是Photoshop里图层的想法。
- 单一职责原则 (SRP)
不应该直接某些属性给用户,这时候我们可以把它们设置成私有的,然后用
property
装饰器包装一下一个函数来返回这个属性。除了保护数据以外,这种实现还提供了些其他有用的特性,因为本质上是一个函数。property
装饰器相比于传统面向对象的object.get_attribute
方法,可以使得代码更加直观和简洁,符合逻辑。属性设置器还可以添加逻辑来检查新值是否合法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Person:
def __init__(self, age):
self._age = age
def age(self):
return self._age
def age(self, value):
if not (0 <= value <= 120):
raise ValueError("Age must be between 0 and 120")
self._age = value
person = Person(30)
person.age = 150 # 将抛出一个 ValueError因为装饰器是装饰到函数上的,因此可以创建一个虚拟属性,它的值是通过计算得到的,而不是直接存储的
1
2
3
4
5
6
7
8
9
10
11class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rect = Rectangle(3, 4)
print(rect.area) # 输出: 12可以用属性来实现懒加载,即只在首次访问属性时计算它的值,然后将结果存储起来,而不是在构造函数中就计算它的值,以提升访问速度:
1
2
3
4
5
6
7
8
9
10
11
12
13
14class ExpensiveObject:
def __init__(self):
self._data = None
def data(self):
if self._data is None:
# 假设这个操作非常耗时
self._data = "expensive operation result"
return self._data
obj = ExpensiveObject()
print(obj.data) # 第一次访问时进行计算
print(obj.data) # 后续访问使用缓存的结果
工作区其实本质上跟Git管理是没有关系的,
git
文件所在的目录就叫作工作区,工作区里的文件分为两种,一种是已经被添加到Git索引中的文件,我们叫作已跟踪文件,他们可以是未修改的,已修改的或者已暂存的。剩下的还有未跟踪文件,这些是还未被添加到Git索引中的新文件或者已经从索引中删除的文件,他们不属于Git管理的部分。所以git reset
将不会对没被添加到Git索引中的新文件产生任何影响,因为它只能管理被Git纳入到管理系统中的文件。测试驱动开发(TDD, Test-Driven Development) + 面向接口编程(IOP, Interface-Oriented Programming),看起来把二者结合使用可以让系统更加健壮一点。先定义良好的接口,这些接口描述了一个类或组件的行为,而不是实现细节。然后根据这些接口,编写测试,应该满足预期的功能。然后编写代码,来通过根据接口编写的测试用例。每个函数的单元测试至少要包括三种情况,内部情况、边界情况、极端情况。
在GitHub中,一个issue(或pull request)只能与一个milestone关联,但一个milestone可以关联多个issues和/或pull requests。
使用milestone的目的是为了组织和跟踪某个特定目标或项目阶段的进度。比如,你可能为一个软件版本、一个特定的功能集或一个项目阶段创建一个milestone。然后,你可以将与这个目标或阶段相关的所有issues和pull requests都关联到这个milestone上。
这样做的好处是:
- 清晰地组织任务:Milestone可以帮助你更清晰地组织和跟踪项目的关键阶段或目标。
- 方便的进度跟踪:你可以一目了然地看到与某个milestone关联的所有任务的完成情况,从而知道该目标或阶段的完成进度。
- 鼓励团队合作:当团队成员可以看到某个milestone的完成进度,他们可能会更有动力为达到该目标而共同努力。
因此,当你在项目中设定目标或重要阶段时,为其创建milestone,然后将与其相关的所有issues和pull requests关联到这个milestone上,是一个非常有效的管理和跟踪工具。
milestone是我们要开发实现的一个大的功能,而issue是某一个具体的任务。
我们可以建立一个
.githut/ISSUE_TEMPLATE
目录,然后在里面写一个markdown文件,用于特定issue请求的模板。原来
git stash
是分支无关的,另外在rebase
到main
分支的最新更改之前,别忘了把stash
的更改先吐出来,以防发生冲突啊。是的,你说得对。
git stash
是分支无关的,这意味着你可以在一个分支上进行stash
操作,然后切换到另一个分支并使用git stash apply
或git stash pop
来应用之前保存的更改。简单来说,git stash
的作用是临时保存你的工作进度,让工作目录恢复到一个干净的状态。这在你需要临时切换到另一个任务,但又不想提交半成品代码的时候特别有用。当你完成其他任务并回到原来的工作上时,你可以使用git stash apply
或git stash pop
来恢复你之前的工作进度,无论你此时在哪个分支上。在开发的时候不要用
pip install
安装包,否则会覆盖当前最新更改的目录。其实可以反过来写自己的实现,如果面向接口编程的话,完全可以看看Comsol的Python-API是怎么写的,按照同样类似的流程去建起一个结构。
如果属性之间有依赖关系,想要返回的时候同步更新,用
@lru_cache
+@property
可以实现这个想法. 比如一个高层对象有一个低层对象,想在高层对象上直接暴露一个接口,返回底层对象某个属性的值,这时就可以用property装饰器包装一个假的对象。如果要修改也是可以的,再提供一个函数,用属性.setter装饰器包装。不是文件冲突,而是修改冲突.
单元测试的组织,一个函数一个测试,针对这个函数不同情况下的反应,把测试分成不同的段。每次想补充一个函数的新情况下的测试,就在同一个测试函数里继续增加。
场协同。。我们做的事情,和评价它的好坏,分别是两套坐标系。。我们要让场协同。。
在读敏捷软件开发的时候,频繁地看到UML类图,对内容产生了误解以后,才发现我看错了,那些箭头并不全都表示继承关系。在UML图,即统一建模语言图中。
具体类在类图中用矩形框表示,矩形框分为三层,第一层是类名字,第二层是类的成员变量,第三层是类的方法。加号公开,减号私有。
抽象类同样用矩形框表示,但是抽象类和抽象方法的名字都用斜体字表示。
接口还是用矩形框表示,但是第一层顶端有一个《《interface》》,而且第二层只有方法。
类实现了一个接口,用虚线+空箭头指向接口
子类继承了父类,用实现+空箭头指向接口
一个类的属性是另一个类,用实现+箭头指向属性类
如果一个类用依赖了另一个类,但另一个类并不是它的属性,那么就用虚线+箭头指向依赖类
聚合是较强的关联关系,强调整体与部分的关系,这从语法上是没法区分的,只能从语义上来区分。例如雁群和大雁的关系,学校和学生的关系,这时候,还是从整体类指向个体类,但是这时候整体类那里要加一个空心菱形。
组合是一种更强的聚合关系,强调了整体和部分的生命周期是一致的,比如大雁和翅膀的关系。这时候还是用一个箭头,但是大雁那里要加一个实心菱形。
今天读了敏捷软件开发的
- 第8章 单一职责原则(SRP)
- 第9章 开放封闭原则(OCP)
- 第10章 Liskov替换原则(LSP)
感到学习到了很多知识,尤其很多内容深有体会,我要在这里总结一下。
首先,单一职责原则,经典的例子是
Rectangle
类的例子,最初的设计,Rectangle
类具有两个方法,一个方法把矩形绘制在屏幕上,另一个方法计算矩形的面积。如果有一个是有关计算几何学方面的它不需要绘制,那么这个就不好。所以我们可以把两个职责分离到两个类当中,先定义一个GeometryRectangle
类,然后Rectangle
类可以把GeometryRectangle
类作为它的一个属性。蒙特卡洛程序里也可以直接用到这个原则啊,我之前一直觉得,我把
strategy
这个属性赋值给Surface
这个类是很有问题的,因为Surface
这个类除了几何,有耦合了边界条件。这时可以先定义一个GeometricSurface
类,然后再派生出Surface
类。是组合还是派生?我觉得对于我这个情况其实都还好,因为几何部分我全都要啊。OCP开放封闭原则,这和我那天写的Copy和蒙特卡洛的失败的例子是差不多的,一个方案就是用抽象类+具体实现类的方案,另一个方案可以用表格驱动。关键就是把根据需求,把不变的东西以某种方式抽出来形成一个单独的东西。哪些东西才是不变的呢?我们愿意被第一颗子弹击中,然后我们确保自己不再被同一只枪发射的其他任何子弹击中。
这是一种有效的对待软件设计的态度。我们最初编写代码时,假设变化不会发生。当变化发生时,我们就创建抽象来隔离以后发生的同类变化。
No Silver Bullet,这些技术,一定是根据具体情况具体分析的。
LSP,Liskov替换原则,把测试中的基类替换成子类,必须全部通过。最简单的例子就是C++中没有声明虚函数,微妙一点,就是即使声明了虚函数,但是逻辑上子类不再和基类在任何情况下都保持一致了,比如长方形和正方形。
LSP让我们得出一个非常重要的结论:一个模型,如果孤立地看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来体现。这和之前写到的面向对象和面向接口一样,IS-A关系是就行为方式而言的,而行为方式是客户程序所依赖的。
LSP启发我们,关键是要抽象出真正的基类,比如线段继承自直线,就很有可能出问题。而如果定义一个线性类,然后分别派生直线和线段,就会好很多。用提取公共部分的方法代替继承。
明天不能再读书了,否则就消化不了了,再写点程序吧,先用Si的那个简单熟悉的色散,更改成第一性类型的,然后算出一组儿。。
绝不能再让国家的重大利益依赖于那些会动摇人类薄弱意志的众多可能性。
高层模块不应该依赖于低层模块。二者都应该依赖于抽象。使用组合和委托的方式是不是高层模块依赖于低层模块呢?
使用组合(Composition)和委托(Delegation)的类关系并不意味着高层模块必然依赖于底层模块,但它确实意味着某种程度的依赖。关键是如何定义并理解“依赖”。
首先,我们来分析这两种关系:
- 组合 (Composition): 当一个类(我们称之为高层模块)包含另一个类的实例(称之为底层模块)作为其属性,那么我们说这两个类之间存在组合关系。例如,一个
Car
类可以包含一个Engine
类的实例。在这种情况下,Car
确实依赖于Engine
,因为Car
的某些行为可能依赖于Engine
的状态和行为。 - 委托 (Delegation): 委托发生在一个对象将其某些职责传递给另一个对象。例如,
PrinterManager
类可能有一个打印方法,但它实际上将这个打印任务委托给了Printer
类。这种情况下,PrinterManager
确实依赖于Printer
来完成打印任务。
然而,关键点在于:依赖的程度和如何管理这种依赖。
- 依赖倒置原则 (Dependency Inversion Principle, DIP): 该原则建议高层模块不应该依赖于底层模块,它们都应该依赖于抽象。换句话说,细节应该依赖于抽象,而不是反过来。这可以通过使用接口或抽象类来实现。例如,如果
Car
依赖于一个抽象的IEngine
接口,而不是一个具体的Engine
类,那么我们可以说Car
是依赖于IEngine
这个抽象,而不是任何具体的Engine
实现。
这样,当我们说高层模块依赖于底层模块时,这种依赖应该是对抽象的依赖,而不是对具体实现的依赖。这有助于降低耦合度,提高模块之间的灵活性,并允许更容易地进行更改和扩展。
看起来mpi4py不匹配是需要都用conda安装一套并行标准
1 | conda install -c conda-forge mpi4py openmpi |
什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节而改变的真理。它是系统内部的系统——它是隐喻。一个类向另一个类发消息的地方,可以显著应用依赖倒置原则。
用不用类不是有所谓的,依赖关系的倒置才是好的面向设计的标志所在。如果程序的依赖关系是倒置的,它就是面向对象的设计。如果程序的依赖关系不是倒置的,它就是过程化的设计。
:不建议用除了9242以外的一代或二代Intel金牌与铂金。怎么看CPU代数呢?Intel的CPU看型号的第二个数字。举个例子铂金8176是Intel的一代产品,6248R是二代的。相同核数的Intel与AMD霄龙对比,在CFD这方面,AMD要快很多。9242是个例外,它的性能可以与相同核数的AMD持平,甚至超过128核的amd,并且价格较低,比较推荐
两台处理器都是英特尔第二代至强可扩展处理器,旧服务器是
96 Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz,24核48线程,组了4颗CPU
新服务器是
96 Intel(R) Xeon(R) Platinum 9242 CPU @ 2.30GHz,48核96线程,每个节点2颗CPU,一共有两个节点。
要理解多态部署在哪一个层级。
抽象基类函数不需要定义构造函数,因为看起来继承最好只是接口继承,具体的实现依赖于抽象,而不是抽象依赖于具体的实现,这样的话构造函数只是为了子类实现它的接口,比如要实现对声子的透射反射,DMM界面的构造函数必须初始化模态穿透率。
要保证单元测试的干净。单元测试就是整个程序的缩影,如果单元测试彼此之间是相互依赖的,那么程序之间的解耦带来的干净和好处就没有得到有效的利用。如果测试必然是耦合的,那说明程序设计本身就是耦合的。
来避免多重继承的问题,只要Geometric只定义接口,而没有构造函数,这个初始化方法就很清楚了,大家都区分开了,这时候可以同时利用继承的优点和组合+委托的优点。
看来接口清晰的关键是,混入类和原有的类应该是隔离的。混入类应该混入一些接口,而不是用原有的类实现一些更复杂的功能。您的观点是正确的,混入类更适合提供方法和接口,而不是复杂的内部状态或行为。这是因为混入类的目的是为多个不同的类提供共同的功能,而这些类可能有完全不同的数据和结构。
看起来最简单的耦合方法根本不需要迭代,可以参考Qing Hao+IMEC的方法组合起来,直接用MC仿真多层结构的等效热导率
氮化镓的这个基本的图像是,氮化镓层的最优厚度,取决于它的热扩展能力,最优厚度随着热扩展能力的下降而下降,能者多劳。所以如果考虑了温度依赖的热导率,最优厚度是变大还是变小呢?是变小了,因为热点附近热量更难扩展出去了。
对于任意方向和位置的抽样,关键就是先抽再线性变换,而不是先线性变换再抽样。
博士学术类答辩的PPT多写技术问题,而不要从叠工作量的角度来写。
对于像全带MC这种与界面方向高度相关的属性问题,为了节约计算量,可以使用lru_cache做一个缓存。这样就不需要每个方向的属性都存成一个性质了。
环形引用就是,一个模块只能依赖于另一个模块,而另一个模块不能依赖于这个模块,层级和依赖关系是单向的。如果发现了环形引用,这个时候要想一想层级关系。
模块化不光是优秀软件设计的核心所在,也是任何优秀工程的核心所在,所以人类才能搭建起来相当复杂的东西,好的模块化让我们只需要理解一个大工程中的一小部分。
git lfs可以托管仓库中的大型文件。
对的,
/*
与/**
在Git和许多其他类Unix系统中的用法中确实有显著的差异,特别是当用于匹配路径和文件时。这两者的差异关键在于它们如何匹配目录结构中的文件:/*
用于匹配指定目录下的所有文件和文件夹,但不会递归匹配更深层次的目录中的文件。/**
用于递归匹配指定目录及其所有子目录中的文件。这意味着如果你希望Git LFS跟踪一个目录及其所有子目录下的文件,你应该使用这种模式。
所以,在
.gitattributes
文件中使用database/**
而不是database/*
确保了Git LFS会跟踪database
目录及其任意深度子目录中的所有文件。这对于确保所有需要的文件都由Git LFS管理非常重要,尤其是在涉及具有复杂目录结构的项目时。如果你的目标是确保
database
目录下的所有文件和子目录中的文件都被Git LFS管理,更新.gitattributes
文件以使用database/**
模式是正确的做法。这应该解决你的问题,并确保git lfs ls-files
能正确列出所有由LFS跟踪的文件。try-except+日志,可以帮助发现问题