了解函数式编程:从表象到本质,从技法到心法

今天看到,《代码整洁之道》(Clean Code)和《架构整洁之道》(Clean Architecture)的作者Robert C. Martin在讨论函数式编程时曾提到:

函数式编程不仅仅是“用函数编程”。函数式编程是没有赋值语句的编程。

一旦你尝试不用赋值语句编程,函数式编程的所有其他特性就水到渠成了。你要处理函数,就必须用递归,所有这些东西在你决定不用赋值的那一刻,就自然而然地形成了。所以说,函数式编程就是这么回事。 ——《函数式设计》,Robert C. Martin

于是,萌发了写一篇聊函数式编程文章的想法。

初识函数式编程

函数式编程(Functional Programming, FP)是一种基于数学函数计算的编程范式。它强调使用纯函数、不可变性和高阶函数来解决问题。本文将通过代码示例和概念解释,带你深入了解函数式编程的核心思想和应用场景。

案例:简单的累加求和

在大部分情况下,循环可以直接用递归来代替:

传统过程式编程实现

def sum_numbers(n):sum = 0for i in range(1, n + 1):sum += ireturn sumprint(sum_numbers(5))  # 输出 15

函数式编程实现

def sum_numbers_functional(n):if n == 1:return 1else:return n + sum_numbers_functional(n - 1)print(sum_numbers_functional(5))  # 输出 15

在这个例子中,通过递归的方式实现了累加求和的功能。没有使用任何赋值语句来更新状态,而是通过每次递归调用自己来逐步累积结果。

案例: 计算斐波那契数列

通常来说,算法都有递推+(iterative)和递归(recursive)两种定义,递推符合我们日常生活的思维模式,递归则是符合函数式编程的思维模式。这两种定义,也对应了下面的传统过程式编程实现和函数式编程实现。

传统过程式编程实现

def fibonacci(n):fib = [0, 1]for i in range(2, n + 1):fib.append(fib[i - 1] + fib[i - 2])return fib[n]print(fibonacci(10))  # 输出 55

函数式编程实现

def fibonacci_functional(n):if n <= 1:return nelse:return fibonacci_functional(n - 1) + fibonacci_functional(n - 2)print(fibonacci_functional(10))  # 输出 55

在这个例子中,我们同样没有使用任何赋值语句来更新状态,而是通过递归来实现斐波那契数列的计算。需要注意的是,这种方法虽然展示了函数式编程的思想,但在实际应用中,递归方法可能不是最优的选择,因为它会导致大量的重复计算。为了解决这个问题,我们可以引入尾递归优化或使用辅助函数来缓存中间结果。

使用尾递归优化斐波那契数列

函数式编程实现(带尾递归)
def fibonacci_tail_recursive(n, a=0, b=1):if n == 0:return aelif n == 1:return belse:return fibonacci_tail_recursive(n - 1, b, a + b)print(fibonacci_tail_recursive(10))  # 输出 55

在这个更复杂的例子中,我们引入了尾递归来优化斐波那契数列的计算。尾递归是一种特殊的递归形式,其中递归调用是函数体中的最后一个操作。这使得编译器或解释器可以在递归调用之前释放当前栈帧,从而防止堆栈溢出。这里使用了两个累积参数 ab 来传递计算的状态,而不是直接修改变量的值。需要注意的是,Python默认并不支持尾调用优化,但在支持尾调用优化的语言中,这种方法可以大大提高程序的效率。


深入函数式编程

函数式编程的几个核心概念:

  1. 纯函数(Pure Functions)

    • 纯函数的输出仅取决于输入参数,没有副作用。相同的输入总是产生相同的输出,并且不修改任何外部状态。
  2. 不可变性(Immutability)

    • 在函数式编程中,一旦创建了一个数据结构,就不能更改它的状态。所有数据都是不可变的,这意味着一旦创建了一个对象或变量,就不能直接修改它的内容,只能通过创建新的数据结构来表示状态的变化。
  3. 高阶函数(Higher-Order Functions)

    • 函数可以作为参数传递给其他函数,也可以作为其他函数的返回值。高阶函数允许我们编写抽象级别更高的代码,使得代码更加模块化和可复用。
  4. 递归(Recursion)

    • 函数式编程中经常使用递归来实现循环逻辑,而不是使用传统的循环结构。递归是一种自然的方式来表达重复的任务,特别是在处理树形结构或分治算法时。
  5. 惰性求值(Lazy Evaluation)

    • 惰性求值是指只有在真正需要的时候才计算表达式的值。这可以提高效率,特别是在处理大量数据时,因为不需要一次性加载所有数据。

为什么能去掉状态的存储?

函数式编程之所以能够去掉状态的存储,主要是因为:

  • 避免了副作用(Side Effects):在函数式编程中,函数不会修改外部状态,这就消除了许多传统编程模式中的不确定性和调试难度。
  • 不变性(Immutability):使用不可变的数据结构可以简化编程模型,因为不必担心数据会在不经意间被修改。
  • 并行性(Concurrency):由于函数式编程中的函数不依赖于外部状态,也不修改外部状态,因此多个函数可以安全地并行执行,无需担心数据竞争条件。

函数式编程的限制

虽然函数式编程有许多优点,但它并不是适用于所有场景的银弹。以下是一些限制和注意事项:

  1. 性能开销

    • 在某些情况下,频繁创建新数据结构可能会导致性能下降。例如,在大规模数据处理中,如果频繁地复制数组或列表,可能会导致内存使用和计算时间的增加。
  2. 递归深度限制

    • 在一些语言中,特别是那些没有尾调用优化(Tail Call Optimization)的语言,如Python,递归可能导致栈溢出。
  3. 学习曲线

    • 对于习惯了命令式编程的开发者来说,理解和掌握函数式编程的概念和技巧可能需要一定的时间。
  4. 调试困难

    • 在某些情况下,由于缺乏显式的状态跟踪,调试函数式程序可能会比调试命令式程序更具挑战性。
  5. 生态系统兼容性

    • 如果你的项目依赖于特定的库或框架,而这些库或框架并没有充分支持函数式编程,那么完全采用函数式编程可能会遇到兼容性问题。

总的来说,函数式编程提供了一种强大的编程范式,可以简化代码、提高代码的可读性和可维护性,但在实际应用中需要根据具体情况权衡其利弊。在实际开发中,结合函数式编程和其他编程范式(如面向对象编程、命令式编程)通常是更灵活的做法。


进一步深入,探讨函数式编程的本质

通过前面几个例子,我们看到许多算法都能转换为函数式编程思想的实现。那么,函数式编程思想的本质是什么?

由于 函数式的优势是:具有不变性从而能避免副作用,具有并行性的优点
因此 函数式编程技术的本质是:为了达到纯函数的要求,利用技术手段将非纯函数转换为纯函数的技术
达到纯函数的技术内涵是问题分解。
因此 函数式编程思想的本质是:问题分解


技术手段实现纯函数

刚刚提到,了达到纯函数的要求,可以利用技术手段将非纯函数转换为纯函数。那么有哪些常见的技术手段将普通函数转换为纯函数呢?

递归实现纯函数

递归是一种非常强大的技术,它通过自我调用来解决更小规模的相同问题,直到达到可以直接解决的基本情况。

  1. 递归实现阶乘

    def factorial(n):if n == 0:return 1else:return n * factorial(n - 1)print(factorial(5))  # 输出 120
    
  2. 递归实现斐波那契数列

    def fibonacci(n):if n <= 1:return nelse:return fibonacci(n - 1) + fibonacci(n - 2)print(fibonacci(10))  # 输出 55
    
  3. 递归实现树的遍历

    data Tree a = Leaf | Node a (Tree a) (Tree a)sumTree :: Tree Int -> Int
    sumTree Leaf = 0
    sumTree (Node value left right) = value + sumTree left + sumTree rightmain = print (sumTree (Node 1 (Node 2 Leaf Leaf) Leaf))  -- 输出 3
    

递归与其他形式的问题分解

递归是问题分解的一种常见形式,但问题分解还包括分治法、分批处理等其他形式。

  1. 分治法(Divide and Conquer)

    def merge_sort(arr):if len(arr) <= 1:return arrmid = len(arr) // 2left = merge_sort(arr[:mid])right = merge_sort(arr[mid:])return merge(left, right)def merge(left, right):result = []while left and right:if left[0] < right[0]:result.append(left.pop(0))else:result.append(right.pop(0))result.extend(left or right)return resultarr = [3, 1, 4, 1, 5, 9, 2, 6]
    print(merge_sort(arr))  # 输出 [1, 1, 2, 3, 4, 5, 6, 9]
    
  2. 分批处理(Chunk Processing)

    def process_large_data(data, batch_size=1000):results = []for i in range(0, len(data), batch_size):batch = data[i:i + batch_size]processed_batch = map(process_item, batch)results.extend(processed_batch)return resultsdef process_item(item):# 处理单个项的逻辑return item * 2large_data = list(range(1, 1001))
    processed_data = process_large_data(large_data)
    print(processed_data[:10])  # 输出 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
    

其他技术手段实现纯函数

  1. 高阶函数(Higher-Order Functions)

    numbers = [1, 2, 3, 4, 5]
    squared = map(lambda x: x**2, numbers)
    print(list(squared))  # 输出 [1, 4, 9, 16, 25]
    
  2. 组合函数(Function Composition)

    def square(x):return x ** 2def add_one(x):return x + 1def compose(f, g):return lambda x: f(g(x))composed = compose(square, add_one)
    print(composed(3))  # 输出 16 (因为 (3 + 1)**2 = 16)
    
  3. 柯里化(Currying)

    def curry(f):return lambda x: lambda y: f(x, y)def add(x, y):return x + ycurried_add = curry(add)
    add_five = curried_add(5)
    print(add_five(3))  # 输出 8
    
  4. 模式匹配(Pattern Matching)

    sumList [] = 0
    sumList (x:xs) = x + sumList xsmain = print (sumList [1, 2, 3])  -- 输出 6
    
  5. 代数数据类型(Algebraic Data Types)

    data Tree a = Leaf | Node a (Tree a) (Tree a)depth :: Tree a -> Int
    depth Leaf = 0
    depth (Node _ left right) = 1 + max (depth left) (depth right)main = print (depth (Node 1 (Node 2 Leaf Leaf) Leaf))  -- 输出 2
    

通过结合递归和其他技术手段,我们可以更好地理解函数式编程的核心思想,并将其应用于实际编程任务中。


函数式编程技巧在我们日常编程活动中也很常见

函数式编程技巧在日常编程活动中非常常见,并且这些技巧不仅限于专门的函数式编程语言。下面通过具体的例子展示如何在日常编程中使用函数式编程技巧,如 mapfilter 等。

使用 map

map 是一种常见的高阶函数,用于将一个函数应用到集合中的每一个元素,并返回一个新的集合。

numbers = [1, 2, 3, 4, 5]# 使用 lambda 表达式
squared = map(lambda x: x**2, numbers)
print(list(squared))  # 输出 [1, 4, 9, 16, 25]# 使用定义的函数
def square(x):return x**2squared = map(square, numbers)
print(list(squared))  # 输出 [1, 4, 9, 16, 25]

使用 filter

filter 是另一种常见的高阶函数,用于筛选集合中的元素,只保留满足某个条件的元素。

numbers = [1, 2, 3, 4, 5]# 使用 lambda 表达式
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))  # 输出 [2, 4]# 使用定义的函数
def is_even(x):return x % 2 == 0even_numbers = filter(is_even, numbers)
print(list(even_numbers))  # 输出 [2, 4]

使用 reduce(或 fold

reduce 是一个高阶函数,用于将一个函数应用于一个序列中的元素,以将其缩减为单一的输出值。

from functools import reducenumbers = [1, 2, 3, 4, 5]# 使用 lambda 表达式
total = reduce(lambda x, y: x + y, numbers)
print(total)  # 输出 15# 使用定义的函数
def add(x, y):return x + ytotal = reduce(add, numbers)
print(total)  # 输出 15

使用 list comprehension

列表推导式(List Comprehensions)是一种简洁的语法,用于创建新的列表。

numbers = [1, 2, 3, 4, 5]# 使用 list comprehension
squared = [x**2 for x in numbers]
print(squared)  # 输出 [1, 4, 9, 16, 25]

函数式编程技巧在日常编程中非常实用,通过使用 mapfilterreduce 以及列表推导式等技巧,我们可以更好地处理数据结构和逻辑运算,从而构建更加健壮和高效的程序。这些技巧不仅适用于纯粹的函数式编程语言,也可以很好地融入到混合编程范式中。


从技法到心法:函数式编程的思维模式

函数式编程不仅涉及具体的编程技法,更是一种思维方式。以下是从具体的编程技法过渡到更深层次的心法的一些要点:

技法:具体编程技巧

  1. 高阶函数(Higher-Order Functions)

    • 使用 mapfilterreduce 等高阶函数可以让你的代码更加简洁和易于理解。这些函数可以将复杂操作分解为更小的、可组合的函数。
  2. 不可变性(Immutability)

    • 使用不可变数据结构可以避免状态的意外改变,从而使代码更容易推理和维护。
  3. 递归(Recursion)

    • 递归是一种自然的问题分解方法,可以将复杂问题简化为更小的子问题,直到达到可以直接解决的基本情况。
  4. 模式匹配(Pattern Matching)

    • 模式匹配可以帮助你根据不同情况执行不同的逻辑,特别是在处理代数数据类型时。
  5. 柯里化(Currying)

    • 柯里化可以将多参数函数转换为一系列单参数函数,从而更容易组合和重用。

心法:思维方式

  1. 纯函数(Pure Functions)

    • 心法:培养“无副作用”的思维方式。纯函数的输出仅依赖于输入参数,不依赖外部状态,也不改变外部状态。这种思维方式有助于构建可预测、易于测试的代码。
  2. 数据为中心(Data-Centric Thinking)

    • 心法:将注意力集中在数据流上,而不是控制流。在函数式编程中,数据通过一系列纯函数流动,而不是通过状态的改变。这种思维方式可以让你更专注于数据处理逻辑,而不是控制逻辑。
  3. 函数组合(Function Composition)

    • 心法:将复杂问题分解为更简单的子问题,并通过组合简单的函数来构建复杂的逻辑。这种思维方式鼓励你从整体到局部逐步构建解决方案。
  4. 惰性求值(Lazy Evaluation)

    • 心法:延迟计算,只在需要时才进行计算。这种思维方式有助于提高效率,特别是在处理大量数据时,可以避免不必要的计算。
  5. 抽象化(Abstraction)

    • 心法:将常见模式抽象化为通用的函数或类型。例如,通过高阶函数将常见的操作抽象化为通用的处理流程。这种思维方式有助于提高代码的复用性和可维护性。
  6. 分治法(Divide and Conquer)

    • 心法:将问题分解为更小的子问题,逐步解决。这种思维方式不仅适用于递归,还可以应用于其他问题解决策略,如分批处理、分治算法等。
  7. 函数作为第一类公民(Functions as First-Class Citizens)

    • 心法:将函数视为与其他数据类型同等重要的实体。这种思维方式鼓励你在编程中充分利用函数的能力,将函数作为参数传递,或将函数作为返回值使用。
  8. 并行思维(Parallel Thinking)

    • 心法:函数式编程中的纯函数和不可变性使得代码更容易并行执行。这种思维方式有助于构建可扩展、高性能的应用程序。

实践案例

示例:使用函数式编程思维处理数据流

假设我们需要从一个列表中筛选出所有的偶数,并计算它们的平方和。

传统过程式编程实现

numbers = [1, 2, 3, 4, 5, 6]def process_numbers(nums):even_numbers = []for num in nums:if num % 2 == 0:even_numbers.append(num)squared_sum = sum([num**2 for num in even_numbers])return squared_sumresult = process_numbers(numbers)
print(result)  # 输出 56

函数式编程实现

from functools import reducenumbers = [1, 2, 3, 4, 5, 6]def process_numbers(nums):even_numbers = filter(lambda x: x % 2 == 0, nums)squared_numbers = map(lambda x: x**2, even_numbers)squared_sum = reduce(lambda x, y: x + y, squared_numbers)return squared_sumresult = process_numbers(numbers)
print(result)  # 输出 56

通过掌握函数式编程的思维模式,你可以更自然地运用函数式编程的技法。从具体的编程技巧过渡到更深层次的思维方式,可以帮助你构建更健壮、可维护的代码。函数式编程不仅仅是关于如何编写代码,更是关于如何思考和解决问题。通过培养这些思维方式,你可以更好地应对复杂的编程挑战,并提高代码的整体质量。


总结

函数式编程提供了一种强大的编程范式,通过纯函数、不可变性、高阶函数等核心概念,可以简化代码、提高代码的可读性和可维护性。虽然函数式编程具有一定的学习曲线和性能开销,但在实际开发中结合函数式编程和其他编程范式通常是更灵活的做法。

通过这篇文章,我们了解了函数式编程的基本概念和核心技术,探讨了函数式编程的本质,并通过实际案例展示了如何在日常编程中应用函数式编程技巧。希望本文能够帮助你更好地理解和应用函数式编程,提高编程效率和代码质量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/54571.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TaskRes: Task Residual for Tuning Vision-Language Models

文章汇总 当前VLMs微调中存在的问题 提示微调的问题 在提示调优中缺乏对先验知识保存的保证(me&#xff1a;即提示微调有可能会丢失预训练模型中的通用知识)。虽然预先训练的文本分支模块(如文本编码器和投影)的权重在提示调优范式中被冻结&#xff0c;但原始的良好学习的分类…

BUUCTF-MISC-荷兰宽带数据泄露

下载附件得到一个二进制文件 通过题目猜测这是一段路由器备份日志&#xff0c;可以使用RouterPassView打开 链接: https://pan.baidu.com/s/1tY5Sdl8GcI5dKQdhPXj5yA?pwdhi9k 下载链接http://pan.baidu.com/s/1tY5Sdl8GcI5dKQdhPXj5yA?pwdhi9k注意&#xff0c;这个软件会报毒…

ARPGDemo第一阶段

1、阶段展示 ARPGDemo阶段展示1 2、使用技术 1.资源使用 开发阶段使用AssetDatabase.LoadAssetAtPath在Editor加载使用&#xff0c;当需要导包将切换AssetBundles来Build并使用加载。 2.加载详情 项目中开始界面UI以及场景的加载均使用异步加载。其中场景切换异步加入了异步…

struts2 S2-057远程执行代码漏洞 靶场攻略

环境 vulhub靶场 /struts2/s2-057 漏洞简介 漏洞产⽣于⽹站配置XML时如果没有设置namespace的值&#xff0c;并且上层动作配置中并没有设置 或使⽤通配符namespace时&#xff0c;可能会导致远程代码执⾏漏洞的发⽣。同样也可能因为url标签没 有设置value和action的值&…

react + antDesign封装图片预览组件(支持多张图片)

需求场景&#xff1a;最近在开发后台系统时经常遇到图片预览问题&#xff0c;如果一个一个的引用antDesign的图片预览组件就有点繁琐了&#xff0c;于是在antDesign图片预览组件的基础上二次封装了一下&#xff0c;避免重复无用代码的出现 效果 公共预览组件代码 import React…

JBoss EJBInvokerServlet CVE-2013-4810 反序列化漏洞

vulhub/jboss/JMXInvokerServlet-deserialization 1. 此漏洞存在于JBoss中 /invoker/JMXInvokerServlet 路径。访问若提示下载 JMXInvokerServlet&#xff0c;则可能存在漏洞&#xff1a; #创建class⽂件 javac -cp .:commons-collections-3.2.1.jar ReverseShellCommonsCol…

SpringBoot开发——整合Apache POI轻松生成精美的Excel报表

文章目录 1、准备工作2、编写代码2.1 创建实体类2.2 创建Excel生成服务2.3 创建控制器 3、测试4、结论 在许多企业应用程序中&#xff0c;导出数据到Excel表格是一项常见的需求。Spring Boot提供了许多库来简化这个过程&#xff0c;其中包括Apache POI和Spring Boot的相关模块。…

格力嵌入式面试题及参考答案

break 和 return 的区别 break 和 return 在编程语言中都用于控制程序的流程,但它们有很大的区别。 break 主要用于循环语句(如 for 循环、while 循环)和 switch 语句中。在循环中,当遇到 break 语句时,立即终止当前循环,程序将从循环后的下一条语句继续执行。例如在一个…

Qt 模型视图(四):代理类QAbstractItemDelegate

文章目录 Qt 模型视图(四):代理类QAbstractItemDelegate1.基本概念1.1.使用现有代理1.2.一个简单的代理 2.提供编辑器3.向模型提交数据4.更新编辑器的几何图形5.编辑提示 Qt 模型视图(四):代理类QAbstractItemDelegate ​ 模型/视图结构是一种将数据存储和界面展示分离的编程方…

Python国产新 ORM 框架 fastzdp_sqlmodel 快速入门教程

创建模型 from typing import Optional from sqlmodel import Field, SQLModel import fastzdp_sqlmodel as fasmclass Hero(SQLModel, tableTrue):id: Optional[int] Field(defaultNone, primary_keyTrue)name: strsecret_name: strage: Optional[int] None创建表 from ty…

系统架构设计师:软件可靠性

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 系统架构设计师:软件可靠性前言软件可靠性可靠性的目标可以用如下方面来描述可靠性测…

IO流笔记

Java中的IO操作大体能分成两种&#xff1a;输出流和输入流&#xff0c;根据使用的流的方式&#xff0c;又可分为字符流和字节流。 1.读数据流 Reader和InputStream&#xff0c;它们都是抽象类&#xff0c;必须由其子类实例化。 实例化&#xff1a;FileReader和FileInputStream&…

【C语言零基础入门篇 - 7】:拆解函数的奥秘:定义、声明、变量,传递须知,嵌套玩转,递归惊艳

文章目录 函数函数的定义与声明局部变量和全局变量、静态变量静态变量和动态变量函数的值传递函数参数的地址传值 函数的嵌套使用函数的递归调用 函数 函数的定义与声明 函数的概念&#xff1a;函数是C语言项目的基本组成单位。实现一个功能可以封装一个函数来实现。定义函数的…

DPDK 简易应用开发之路 3:实现ping(ARP ICMP 协议)

本机环境为 Ubuntu20.04 &#xff0c;dpdk-stable-20.11.10 网卡IP为192.168.131.153 mac地址 00 0c 29 00 04 4d 理论基础 机器A内部没有机器B的物理地址&#xff0c;则 A ping B 的时候需要先发 arp 请求&#xff0c;以获取机器 B 的 MAC 地址。 获取 MAC 地址 如果 A 和 …

python画图1

import matplotlib.pyplot as pltplt.rcParams["font.sans-serif"] ["SimHei"]# 模拟数据 years [2016, 2017, 2018, 2019, 2020, 2021, 2022] market_size [7950, 8931, 9940, 11205, 12305, 13199, 14980] my_color #3e9df5plt.plot(years, market_s…

ER论文阅读-Decoupled Multimodal Distilling for Emotion Recognition

基本介绍&#xff1a;CVPR, 2023, CCF-A 原文链接&#xff1a;https://openaccess.thecvf.com/content/CVPR2023/papers/Li_Decoupled_Multimodal_Distilling_for_Emotion_Recognition_CVPR_2023_paper.pdf Abstract 多模态情感识别&#xff08;MER&#xff09;旨在通过语言、…

spring-boot-maven-plugin插件打包和java -jar命令执行原理

文章目录 1. Maven生命周期2. jar包结构2.1 不可执jar包结构2.2 可执行jar包结构 3. spring-boot-maven-plugin插件打包4. 执行jar原理 1. Maven生命周期 Maven的生命周期有三种&#xff1a; clean&#xff1a;清除项目构建数据&#xff0c;较为简单&#xff0c;不深入探讨&a…

面试速通宝典——1

1. 内存有哪几种类型&#xff1f; ‌‌‌‌  内存分为五个区&#xff0c;堆&#xff08;malloc&#xff09;、栈&#xff08;如局部变量、函数参数&#xff09;、程序代码区&#xff08;存放二进制代码&#xff09;、全局/静态存储区&#xff08;全局变量、static变量&#…

Gitlab学习(008 gitlab开发工作流GitFlow)

尚硅谷2024最新Git企业实战教程&#xff0c;全方位学习git与gitlab 总时长 5:42:00 共40P 此文章包含第27p-第p29的内容 文章目录 工作流分类集中式工作流功能开发工作流GitFlow工作流Forking工作流 各个分支的功能模拟工作环境创建分支登录领导&#xff08;项目管理者&#…

idea插件开发的第五天-今天不写工具

介绍 今天介绍一款插件,可以帮你调用spring容器里面的方法,并且可以执行脚本 Demo说明 本文基于maven项目开发,idea版本为2022.3以上,jdk为1.8本文在Tools插件之上进行开发本次demo将使用idea的一些组件优化 Tools插件说明 Tools插件是一个Idea插件,此插件提供统一Spi规范…