Return to Fluent C Table of Contents, Fluent C, C Language Design Patterns, C Language, C Language Bibliography, C Language Courses, C Language DevOps - C Language CI/CD, C Language Security - C Language DevSecOps, C Language Functional Programming, C Language Concurrency, C Language Data Science - C Language and Databases, C Language Machine Learning, C Language Glossary, Awesome C Language, C Language GitHub, C Language Topics
You picked up this book to move your programming skills one step forward. That is good, because you'll definitely benefit from the hands-on knowledge provided in this book. If you have a lot of experience programming in C, you'll learn the details of good design decisions and about their benefits and drawbacks. If you are fairly new to C programming, you'll find guidance about design decisions, and you'll see how these decisions are applied bit by bit to running code examples for building larger scale programs.
The book answers questions such as how to structure a C program, how to cope with error handling, or how to design flexible interfaces. As you learn more about C programming, questions often pop up, such as the following:
• Should I return any error information I have?
• Should I use the global variable errno to do that?
• Should I have few functions with many parameters or the other way around?
• How do I build a flexible interface?
• How can I build basic things like an iterator?
For object-oriented languages, most of these questions are answered to a great extent by the Gang of Four book Design Patterns - Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Prentice Hall, 1997). Design patterns provide a programmer with best practices on how objects should interact and which object owns which other kinds of objects. Also, design patterns show how such objects can be grouped together.
However, for procedural programming languages like C, most of these design patterns cannot be implemented in the way described by the Gang of Four. There are no native object-oriented mechanisms in C. It is possible to emulate inheritance or polymorphism in the C programming language, but that might not be the first choice, because such emulation makes things unfamiliar for programmers who are used to programming C and are not used to programming with object-oriented languages like C++ and using concepts like inheritance and polymorphism. Such programmers may want to stick to their native C programming style that they are used to. However, with the native C programming style, not all object-oriented design patterns guidance is usable, or at least the specific implementation of the idea presented in a design pattern is not provided for non-object-oriented programming languages.
And that is where we stand: we want to program in C, but we cannot directly use most of the knowledge documented in design patterns. This book shows how to bridge this gap and implement hands-on design knowledge for the C programming language.
Let me tell you why the knowledge gathered in this book turned out to be very important for me and why such knowledge is hard to find.
In school I learned C programming as my first programming language. Just like every new C programmer, I wondered why arrays start with index 0, and I first rather randomly tried out how to place the operators * and & in order to finally get the C pointer magic working.
At university I learned how C syntax actually works and how it translates to bits and bytes on the hardware. With that knowledge I was able to write small programs that worked very well. However, I still had trouble understanding why longer code looked the way it did, and I certainly wouldn't have come up with solutions like the following:
typedef struct INTERNAL_DRIVER_STRUCT* DRIVER_HANDLE; typedef void (*DriverSend_FP)(char byte);
typedef char (*DriverReceive_FP)();
typedef void (*DriverIOCTL_FP)(int ioctl, void
{
DriverSend_FP fpSend;
};
DRIVER_HANDLE driverCreate(void
void sendByte(DRIVER_HANDLE h, char byte);
char receiveByte(DRIVER_HANDLE h);
void driverIOCTL(DRIVER_HANDLE h, int ioctl, void
Looking at code like that prompted many questions:
• Why have function pointers in the struct?
• Why do the functions need that DRIVER_HANDLE?
• What is an IOCTL, and why would I not have separate functions instead?
• Why have explicit create and destroy functions?
These questions came up as I began writing industrial applications. I regularly came across situations where I realized I did not have the C programming knowledge, for example, to decide how to implement an iterator or to decide how to cope with error handling in my functions. I realized that although I knew C syntax, I had no clue how to apply it. I tried to achieve something but just managed to do it in a clumsy way or not at all. What I needed were best practices on how to achieve specific tasks with the C programming language. For example, I needed to know things like the following:
• How can I acquire and release resources in an easy way?
• Is it a good idea to use goto for error handling?
• Should I design my interface to be flexible, or should I simply change it when the need arises?
• Should I use an assert statement, or should I return an error code?
• How is an iterator implemented in C?
It was very interesting for me to realize that while my experienced work colleagues had many different answers for these questions, nobody could point me to anything that documented these design decisions and their benefits and drawbacks.
So next I turned to the internet, and yet again I was surprised: it was very hard to find sound answers to these questions even though the C programming language has been around for decades. I found out that while there is much literature on the C programming language basics and its syntax, there's not much on advanced C programming vs or how to write beautiful C code that holds up to industrial applications.
And that is exactly where this book comes in. This book teaches you how to advance your programming skills from writing basic C programs to writing larger-scale C
programs that consider error handling and that are flexible regarding certain future changes in requirements and design. This book uses the concept of design patterns to provide you bit by bit with design decisions and their benefits and drawbacks. These design patterns are applied to running code examples that teach you how code like the earlier example evolves and why it ends up looking the way it does.
Preface | ix
The presented patterns can be applied to any C programming domains. As I come from the domain of embedded programming in a multithreaded real-time environment, some of the patterns are biased towards that domain. Anyways, you'll see that the general idea of the patterns can be applied to other C programming domains and even beyond the scope of C programming.
The design guidance in this book is provided in the form of patterns. The idea of presenting knowledge and best practices in the form of patterns comes from the architect Christopher Alexander in The Timeless Way of Building (Oxford University Press, 1979). He uses small pieces of well-proven solutions to tackle a huge problem in his domain: how to design and construct cities. The approach of applying patterns was adopted by the software development domain, where pattern conferences like the conference on Pattern Languages of Programs (PLoP) are held to extend the body of knowledge of patterns. In particular, the book Design Patterns - Elements of Reusable Object-Oriented Software by the Gang of Four (Prentice Hall, 1997) had a significant impact and made the concept of design patterns well known to software developers.
But what exactly is a pattern? There are many definitions out there, and if you are deeply interested in the v, then the book Pattern-Oriented Software Architecture: On Patterns and Pattern Languages by Frank Buschmann et al. (Wiley, 2007) can provide you with accurate descriptions and details. For the purposes of this book, a pattern provides a well-proven solution to a real-life problem. The patterns presented in this book have the structure shown in Table P-1.
Table P-1. How patterns are broken down in this book
This is the name of the pattern, which should be easy to remember. The aim is that this name will be used by programmers in their everyday language (as is the case with the Gang of Four patterns, where you hear programmers say, “And the Abstract Factory creates the object”). Pattern names are capitalized in this book.
The context section sets the scene for the pattern. It tells you under which circumstances this pattern can be applied.
The problem section gives you information about the issue you want to tackle. It starts with the major problem statement written in bold font type and then adds details on why the problem is hard to solve. (In other pattern formats, these details go into a separate section called “forces.”) Solution
This section provides guidance on how to tackle the problem. It starts with stating the main idea of the solution written in bold font type and continues with details about the solution. It also provides a code example in order to give very concrete guidance.
This section lists the benefits and drawbacks of applying the described solution. When applying a pattern, you should always confirm that the consequences that arise are OK with you.
The known uses give you evidence that the proposed solution is good and actually works in real-life applications. They also show you concrete examples to help you understand how to apply the pattern.
x | Preface
A major benefit of presenting design guidance in the form of patterns is that these patterns can be applied one after another. If you have a huge design problem, it's hard to find the one guidance document and the one solution that addresses exactly that problem. Instead, you can think of your huge and very specific problem as a sum of many smaller and more generic problems, and you can tackle these problems bit by bit by applying one pattern after the other. You simply check the problem descriptions of the patterns and apply the one that fits your problem and that has consequences you can live with. These consequences might lead to another problem that you can then address by applying another pattern. That way you incrementally design your code instead of trying to come up with a complete up-front design before even writing the first line of code.
You should already know C programming basics. You should know the C syntax and how it works — for example, this book won't teach you what a pointer is or how to use it. This book delivers hints and guidance on advanced vs.
The chapters in this book are self-standing. You can read them in an arbitrary order, and you can simply pick out the vs you are interested in. You'll find an overview of all patterns in the next section, and from there you can jump to the patterns you are interested in. So if you know exactly what you are looking for, you can start right there.
If you are not looking for one particular pattern, but instead want to get an overview of possible C design options, read through Part I of the book. Each chapter there focuses on a particular v, starting with basic vs like error handling and memory managment, and then moving to more advanced and specific vs like interface design or platform-independent code. The chapters each present patterns related to that v and a running code example that shows bit by bit how the patterns can be applied.
Part II of this book shows two larger running examples that apply many of the patterns from Part I. Here you can learn how to build up some larger piece of software bit by bit through the application of patterns.
You'll find an overview of all patterns presented in this book in Tables P-2 through
P-10. The tables show a short form of the patterns that only contains a brief description of the core problem, followed by the keyword “Therefore,” followed by the core solution.
Preface | xi
Table P-2. Patterns for error handling
“Function Split” on The function has several responsibilities, which makes the function hard to read and maintain.
page 6
Therefore, split it up. Take a part of a function that seems useful on its own, create a new function with that, and call that function.
“Guard Clause” on
The function is hard to read and maintain because it mixes pre-condition checks with the main program
page 9
logic of the function. Therefore, check if you have mandatory pre-conditions, and immediately return from the function if these pre-conditions are not met.
“Samurai Principle” When returning error information, you assume that the caller checks for this information. However, the
on page 12
caller can simply omit this check and the error might go unnoticed. Therefore, return from a function victorious or not at all. If there is a situation for which you know that an error cannot be handled, then abort the program.
Code gets difficult to read and maintain if it acquires and cleans up multiple resources at different
Handling” on page places within a function. Therefore, have all resource cleanup and error handling at the end of the
16
function. If a resource cannot be acquired, use the goto statement to jump to the resource cleanup code.
It is difficult to make a piece of code easy to read and maintain if this code acquires and cleans up
on page 19
multiple resources, particularly if those resources depend on one another. Therefore, call resource acquisition functions as long as they succeed, and store which functions require cleanup. Call the cleanup functions depending on these stored values.
“Object-Based Error Having multiple responsibilities in one function, such as resource acquisition, resource cleanup, and
Handling” on page usage of that resource, makes that code difficult to implement, read, maintain, and test. Therefore, put
22
initialization and cleanup into separate functions, similar to the concept of constructors and destructors in object-oriented programming.
Table P-3. Patterns for returning error information
“Return Status You want to have a mechanism to return status information to the caller, so that the caller can react to it.
Codes” on
You want the mechanism to be simple to use, and the caller should be able to clearly distinguish between
page 32
different error situations that could occur. Therefore, use the Return Value of a function to return status information. Return a value that represents a specific status. Both of you as the callee and the caller must have a mutual understanding of what the value means.
On the one hand, the caller should be able to react to errors; on the other hand, the more error information
you return, the more your code and the code of your caller have to deal with error handling, which makes
Errors” on
the code longer. Longer code is harder to read and maintain and brings in the risk of additional bugs.
page 39
Therefore, only return error information to the caller if that information is relevant to the caller. Error information is only relevant to the caller if the caller can react to that information.
You want to return error information, but it's not an option to explicitly Return Status Codes because that
Return Values” implies that you cannot use the Return Value of the function to return other data. You'd have to return that
on page 45
data via Out-Parameters, which would make calling your function more difficult. Therefore, use the Return Value of your function to return the data computed by the function. Reserve one or more special values to be returned if an error occurs.
“Log Errors” on You want to make sure that in case of an error you can easily find out its cause. However, you don't want
page 48
your error-handling code to become complicated because of this. Therefore, use different channels to provide error information that is relevant for the calling code and error information that is relevant for the developer. For example, write debug error information into a log file and don't return the detailed debug error information to the caller.
xii | Preface
Table P-4. Patterns for memory management
“Stack First” on
Deciding the storage class and memory section (stack, heap, …) for variables is a decision every
page 62
programmer has to make often. It gets exhausting if for each and every variable, the pros and cons of all possible alternatives have to be considered in detail. Therefore, simply put your variables on the stack by default to profit from automatic cleanup of stack variables.
Holding large amounts of data and transporting it between function calls is difficult because you have to
on page 65
make sure that the memory for the data is large enough and that the lifetime extends across your function calls. Therefore, put your data into memory that is available throughout the whole lifetime of your program.
“Lazy Cleanup” on Having dynamic memory is required if you need large amounts of memory and memory where you
page 69
don't know the required size beforehand. However, handling cleanup of dynamic memory is a hassle and is the source of many programming errors. Therefore, allocate dynamic memory and let the operating system cope with deallocation by the end of your program.
The great power of using dynamic memory comes with the great responsibility of having to properly
Ownership” on
clean that memory up. In larger programs, it becomes difficult to make sure that all dynamic memory is
page 72
cleaned up properly. Therefore, right at the time when you implement memory allocation, clearly define and document where it's going to be cleaned up and who is going to do that.
Each allocation of dynamic memory might fail, so you should check allocations in your code to react
Wrapper” on page accordingly. This is cumbersome because you have many places for such checks in your code. Therefore,
76
wrap the allocation and deallocation calls, and implement error handling or additional memory management organization in these wrapper functions.
“Pointer Check” on Programming errors that lead to accessing an invalid pointer cause uncontrolled program behavior, and
page 81
such errors are difficult to debug. However, because your code works with pointers frequently, there is a good chance that you have introduced such programming errors. Therefore, explicitly invalidate uninitialized or freed pointers and always check pointers for validity before accessing them.
“Memory Pool” on Frequently allocating and deallocating objects from the heap leads to memory fragmentation.
page 84
Therefore, hold a large piece of memory throughout the whole lifetime of your program. At runtime, retrieve fixed-size chunks of that memory pool instead of directly allocating new memory from the heap.
Table P-5. Patterns for returning data from C functions
“Return Value” on The function parts you want to split are not independent from one another. As usual in procedural
page 95
programming, some part delivers a result that is then needed by some other part. The function parts that you want to split need to share some data. Therefore, simply use the one C mechanism intended to retrieve information about the result of a function call: the Return Value. The mechanism to return data in C copies the function result and provides the caller access to this copy.
C only supports returning a single type from a function call, and that makes it complicated to return
on page 99
multiple pieces of information. Therefore, return all the data with a single function call by emulating by-reference arguments with pointers.
C only supports returning a single type from a function call, and that makes it complicated to return
multiple pieces of information. Therefore, put all data that is related into a newly defined type. Define
103
this Aggregate Instance to contain all the related data that you want to share. Define it in the interface of your component to let the caller directly access all the data stored in the instance.
Preface | xiii
You want to provide information held in large pieces of immutable data from your component to a
caller. Therefore, have an instance (for example, a struct) containing the data to share in static
108
memory. Provide this data to users who want to access it and make sure that they cannot modify it.
You want to provide complex or large data of known size to the caller, and that data is not immutable
(it changes at runtime). Therefore, require the caller to provide a buffer and its size to the function that
111
returns the large, complex data. In the function implementation, copy the required data into the buffer if the buffer size is large enough.
You want to provide complex or large data of unknown size to the caller, and that data is not
on page 116
immutable (it changes at runtime). Therefore, allocate a buffer with the required size inside the function that provides the large, complex data. Copy the required data into the buffer and return a pointer to that buffer.
Table P-6. Patterns for data lifetime and ownership
You want to provide logically related functionality to your caller and make that functionality as easy as
Software-Module” possible for the caller to use. Therefore, keep your functions simple and don't build up state information
on page 123
in your implementation. Put all related functions into one header file and provide the caller this interface to your software-module.
“Software-Module You want to structure your logically related code that requires common state information and make that
with Global State” functionality as easy as possible for the caller to use. Therefore, have one global instance to let your
on page 127
related functions share common resources. Put all functions that operate on this instance into one header file, and provide the caller this interface to your software-module.
You want to provide multiple callers or threads access to functionality with functions that depend on one
Instance” on page another, and the interaction of the caller with your functions builds up state information. Therefore,
132
require the caller to pass an instance, which is used to store resource and state information, along to your functions. Provide explicit functions to create and destroy these instances, so that the caller can determine their lifetime.
“Shared Instance” You want to provide multiple callers or threads access to functionality with functions that depend on one
on page 138
another, and the interaction of the caller with your functions builds up state information, which your callers want to share. Therefore, require the caller to pass an instance, which is used to store resource and state information, along to your functions. Use the same instance for multiple callers and keep the ownership of that instance in your software-module.
Table P-7. Patterns for flexible APIs
“Header Files”
You want functionality that you implement to be accessible to code from other implementation files, but
on page 149
you want to hide your implementation details from the caller. Therefore, provide function declarations in your API for any functionality you want to provide to your user. Hide any internal functions, internal data, and your function definitions (the implementations) in your implementation file and don't provide this implementation file to the user.
“Handle” on
You have to share state information or operate on shared resources in your function implementations, but
page 152
you don't want your caller to see or even access all that state information and shared resources. Therefore, have a function to create the context on which the caller operates and return an abstract pointer to internal data for that context. Require the caller to pass that pointer to all your functions, which can then use the internal data to store state information and resources.
xiv | Preface
It should be possible to call implementations with slightly deviating behaviors, but it should not be
Interface” on
necessary to duplicate any code, not even the control logic implementation and interface declaration.
page 156
Therefore, define a common interface for the deviating functionalities in your API and require the caller to provide a callback function for that functionality, which you then call in your function implementation.
You want to call implementations with slightly deviating behaviors, but you don't want to duplicate any
Control” on code, not even the control logic implementation or the interface declaration. Therefore, add a parameter to
page 159
your function that passes meta-information about the function call and that specifies the actual functionality to be performed.
Table P-8. Patterns for flexible iterator interfaces
You want to make it possible for the user to iterate elements in your data structure in a convenient way, and
Access” on
it should be possible to change internals of the data structure without resulting in changes to the user's code.
page 168
Therefore, provide a function that takes an index to address the element in your underlying data structure and return the content of this element. The user calls this function in a loop to iterate over all elements.
You want to provide an iteration interface to your user which is robust in case the elements change during
Iterator” on
the iteration and which enables you to change the underlying data structure at a later point without
page 172
requiring any changes to the user's code. Therefore, create an iterator instance that points to an element in the underlying data structure. An iteration function takes this iterator instance as argument, retrieves the element the iterator currently points to, and modifies the iteration instance to point to the next element. The user then iteratively calls this function to retrieve one element at a time.
You want to provide a robust iteration interface which does not require the user to implement a loop in the
Iterator” on
code for iterating over all elements and which enables you to change the underlying data structure at a later
page 177
point without requiring any changes to the user's code. Therefore, use your existing data structure — specific operations to iterate over all your elements within your implementation, and call some provided user-function on each element during this iteration. This user-function gets the element content as a parameter and can then perform its operations on this element. The user calls just one function to trigger the iteration, and the whole iteration takes place inside your implementation.
Table P-9. Patterns for organizing files in modular programs
It's easy to include a header file multiple times, but including the same header file leads to compile
on page 189
errors if types or certain macros are part of it, because during compilation they get redefined. Therefore, protect the content of your header files against multiple inclusion so that the developer using the header files does not have to care whether it is included multiple times. Use an interlocked #ifdef statement or a #pragma once statement to achieve this.
“Software-Module Splitting code into different files increases the number of files in your codebase. Having all files in one
Directories” on
directory makes it difficult to keep an overview of all the files, particularly for large codebases. Therefore,
page 192
put header files and implementation files that belong to a tightly coupled functionality into one directory. Name that directory after the functionality that is provided via the header files.
To include files from other software-modules, you have to use relative paths like ../othersoftwaremodule/
Directory” on page file.h. You have to know the exact location of the other header file. Therefore, have one global directory
197
in your codebase that contains all software-module APIs. Add this directory to the global include paths in your toolchain.
Preface | xv
From the directory structure it is not possible to see the dependencies in the code. Any software-module
Component” on
can simply include the header files from any other software-module, so it's impossible to check
page 201
dependencies in the code via the compiler. Therefore, identify software-modules that contain similar functionality and that should be deployed together. Put these software-modules into a common directory and have a designated subdirectory for their header files that are relevant for the caller.
You want to develop, version, and deploy the parts of your codebase independently from one another.
page 207
However, to do that, you need clearly defined interfaces between the code parts and the ability to separate that code into different repositories. Therefore, to use the functionality of another component, copy its API. Build that other component separately and copy the build artifacts and its public header files. Put these files into a directory inside your component and configure that directory as a global include path.
Table P-10. Patterns for escaping
Using different functions for each platform makes the code harder to read and write. The
page 220
programmer is required to initially understand, correctly use, and test these multiple functions in order to achieve a single functionality across multiple platforms. Therefore, use standardized functions that are available on all platforms. If there are no standardized functions, consider not implementing the functionality.
“Isolated Primitives” on Having code variants organized with #ifdef statements makes the code unreadable. It is very
page 224
difficult to follow the program flow, because it is implemented multiple times for multiple platforms. Therefore, isolate your code variants. In your implementation file, put the code handling the variants into separate functions and call these functions from your main program logic, which then contains only platform-independent code.
The function that contains the variants and is called by the main program is still hard to
page 227
comprehend because all the complex
“Abstraction Layer” on
“You want to use the functionality which handles platform variants at several places in your codebase, but you do not want to duplicate the code of that functionality. Therefore, provide an API for each functionality that requires platform-specific code. Define only platform-independent functions in the header file and put all platform-specific
“Split Variant]]
The platform-specific implementations still contain
xvi | Preface
Conventions Used in This Book
The following typographical conventions are used in this book:
Italic Indicates new terms, URLs, email addresses, filenames, and file extensions.
Bold Used to highlight the problem and solution for each pattern.
Used for program listings, as well as within paragraphs to refer to program elements such as variable or function names, databases, data types, environment variables, statements, and keywords.
This element signifies a general note.
This element indicates a warning or caution.
Using Code Examples
The code examples in this book show short code snippets which focus on the core idea to showcase the patterns and their application. The code snippets by themselves won't compile, because to keep it simple several things are omitted (for example, include files). If you are interested in getting the full code which does compile, you can download it from GitHub at https://github.com/christopher-preschern/fluent-c.
If you have a technical question or a problem using the code examples, please send email to bookquestions@oreil y.com.
This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you're reproducing a significant portion of the code.
For example, writing a program that uses several chunks of code from this book does not require permission. Sel ing or distributing examples from O'Reil y books does require permission. Answering a question by citing this book and quoting example Preface | xvii
code does not require permission. Incorporating a significant amount of example code from this book into your product's documentation does require permission.
We appreciate, but generally do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Fluent C by Christopher Preschern (O'Reilly). Copyright 2023 Christopher Preschern, 978-1-492-09733-4.”
If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreil y.com.
“The patterns in this book all present existing code examples which apply these patterns. The following list shows the references to these code examples:” (FlueC 2023)
• B&R Automation Runtime operating system (proprietary and undisclosed code of the company B&R Industrial Automation GmbH)
• B&R Visual Components automation system visualization editor (proprietary and undisclosed code of the company B&R Industrial Automation GmbH)
• NetDRMS data management system
• MATLAB programming and numeric computing platform
• GoAccess real-time web analyzer
• Cloudy physical calculation software
• GNU Compiler Collection (GCC)
• VxWorks real-time operating system
xviii | Preface
• C standard library functions: glibc implementation
• Netdata real-time performance monitoring and visualization system
• Radare reverse engineering framework
• Education First digital learning products
• gzip data compression program
C Language: C Fundamentals, C Inventor - C Language Designer: Dennis Ritchie in 1972; C Standards: ANSI X3J11 (ANSI C); ISO/IEC JTC 1 (Joint Technical Committee 1) / SC 22 (Subcommittee 22) / WG 14 (Working Group 14) (ISO C); C Keywords, C Pointers, C Data Structures - C Algorithms, C Syntax, C Memory Management, C Recursion, C on Android, C on Linux, C on macOS, C on Windows, C Installation, C Containerization, C Configuration, C Compiler, C IDEs (CLion), C Development Tools, C DevOps - C SRE, C Data Science - C DataOps, C Machine Learning, C Deep Learning, C Concurrency, C History, C Bibliography, Manning C Programming Series, C Glossary, C Topics, C Courses, C Standard Library, C Libraries, C Frameworks, C Research, C GitHub, Written in C, C Popularity, C Awesome List, C Versions. (navbar_c)
Design Patterns: Design Patterns - Elements of Reusable Object-Oriented Software by GoF, Awesome Design Patterns, Awesome Software Design, Pattern, Design, Anti-Patterns, Code Smells, Best Practices, Software Architecture, Software Design, Design Principles, Design Patterns Bibliography, C Language Design Patterns, C++ Design Patterns, C# Design Patterns, Golang Design Patterns, Java Design Patterns, JavaScript Design Patterns, Kotlin Design Patterns, Node.js Design Patterns, Python Design Patterns, TypeScript Design Patterns, Scala Design Patterns, Swift Design Patterns. (navbar_designpatterns)
© 1994 - 2024 Cloud Monk Losang Jinpa or Fair Use. Disclaimers
SYI LU SENG E MU CHYWE YE. NAN. WEI LA YE. WEI LA YE. SA WA HE.