Software Development Concepts: Concurrency, Testing, and Design

Concurrency and Asynchronous Operations

Race Condition: Occurs when the outcome of a program depends on the non-deterministic timing of events, such as the order in which threads are processed and executed. This happens when multiple threads share the same resource. To prevent, use proper synchronization and wrap resource access in locks.

Deadlock: A situation where two or more threads are waiting for resources held by each other, preventing further progress. To prevent, use nested locks and a lock hierarchy.

Using await inside a lock block is disallowed because it undermines the thread ownership guarantee of the lock, risks race conditions or deadlocks, and goes against lock code.

Await: When used with an asynchronous operation, it passes the execution of the current method without blocking the thread and allows the thread to perform other tasks while waiting.

Await Example: async Task FetchDataAsync { Console.WriteLine("Fetching Data...."); String data = await GetDataFromServerAsync(); Console.WriteLine($"Data received: {data}"); }

When to use await: Use for I/O-bound tasks like file operations, HTTPS requests, or database queries. When you want to improve responsiveness (e.g., in UI applications) by avoiding blocking operations. When you want to maintain clean and readable asynchronous code.

Debugging and Code Quality

Debugging: Tools include breakpoints, local windows, call stack, step commands, and debugger visualizers.

Guiding principles for finding errors include understanding the problem, breaking it into smaller parts, using debugging tools, version control, testing edge cases, and testing assumptions.

Advantages of self-documenting code include improved readability, reduced maintenance costs, fewer bugs, and better collaboration.

DRY stands for Do Not Repeat Yourself.

SOLID: Single Responsibility, Open/Closed Principle, Liskov Substitution, Interface Segregation, Dependency Inversion.

Documentation and Version Control

Documentation Example: /// <summary> /// whatever you want to say (summary) /// </summary> /// <param name = "a"> summary of a.</param> /// <returns> what is returns<param ref name = "a"/></returns>

Main actions you take with Git: cloning, push, pull, staging changes, committing, and branching.

We use StyleCopAnalyzer to enforce good coding style.

When to test code: Before writing code (TDD), during development, after implementing new features, and after major changes.

Software Development Lifecycle

Main Phases of Software Lifetime: Design and planning, implementation, testing, deployment, maintenance, and updates.

First rule of optimization: Don’t optimize prematurely. Focus on correctness and clarity. Too early optimization will lead to performance bottlenecks or problems.

Design Patterns and Methodologies

MVC: Model (The data, retrieving the data, processing it), View (What the user sees), Controller (listens for user input, processes it, and returns appropriate responses).

Benefits of MVC: Separation of concerns, modularity, and reusability.

TDD: Test-Driven Development. Write tests before any code is written. It is good for: improved code quality, better designs, helps with refactoring, and better software development.

Open Box Testing: Testing the internal structure of the software. The tester has access to the source code. Tests are designed based on knowledge of the system.

Closed Box Testing: Testing the external behavior of the software when the tester does not have access to the internal code. Focus is on correct inputs and outputs without seeing the code.

Lambda Expressions and Code Examples

Lambda: (x) => x + 5. It’s a concise way to define an anonymous function that can be used wherever a delegate or an expression is expected.

Lambda Error: (s) => throw new ArgumentException();

Code Example: List<Person> people = new List<Person> { new Person { Name = "John", Age = 25}, new Person {Name = "Mary", Age = 32} }; var SortedByAge = People.OrderBy(Person=>Person.Age);

Main stakeholder when developing software is the end user; they will ultimately interact with and rely on it.

Blazor is a web framework developed by Microsoft, part of the .NET platform, used for building web applications with C#.

Pair Programming and Data Serialization

Pair Programming: The Driver and Navigator. Value add: improved code quality, fewer bugs caught earlier, knowledge sharing, and enhanced problem-solving.

JSON Example: public String Name { get; set;} public int Age { get; set;} public bool IsStudent { get; set;} { "name" : "Nico Schembri", "age" : 20, "IsStudent": true, } String jsonstring = JsonSerializer.serialize(student); Console.WriteLine(jsonString); //outputs JSON String

Asynchronous Operations and References

Example of using await and async:

private async void Button_Click(object sender, EventArgs e) { Statuslabel.Text = "Processing..."; var result = await Task.Run(() => LongRunningCalculations()); statusLabel.Text = $"Result:{result}"; }

Ref Keyword: Used to pass arguments by reference to a method. When used, the method can modify the value of the argument, and the change is reflected outside the method.

Used for: Modifying caller variables, avoiding copies, and multiple return values. Useful for: updating variables across methods and performance with large objects.

Ref Example: public static void Calculate (ref int a, ref int b) { a = a + b; b = a - b; } Static void Main() { int x = 5, y = 3; Calculate(ref x, ref y); Console.WriteLine($"x:{x}, y:{y}"); // outputs x= 8 and y =5 }

Web Technologies and Networking

HTML Request: Is sent by a client to a server to ask for resources, such as web pages, images, or other client. Key Components: method, URL, HTTP version, headers, and optionally a body.

HTML Response: Is sent by the server back to the client, providing the requested resource or indicating the result. Key Components: HTTP version, status code, status message, headers, and body.

HTML Elements: <html> the root element of an HTML document contains all other elements. <title> My WebPage</title> <h1> Welcome to my Website</h1> </head> <p>This is a paragraph</p> <title> My Webpage</title> and <h1 - h6> are headers, getting smaller as the number gets bigger </body>

CSS: Is a stylesheet language used to control the presentation and layout of a document written in HTML or CSS. Enables users to style webpages, like color, fonts, positioning, etc.

Networking: The primary building blocks for networking are found in the System.Net and System.Net.Sockets namespaces.

Key Components: Socket communication, HTTP and web requests, DNS resolution, and data serialization.

HTTP and web requests example: HttpClient client = new HttpClient(); HttpResponseMessage response = await Client.GetAsync("https://example.com"); Stream contentStream = await response.Content.ReadAsStreamAsync();

TCP/UDP Protocols: Core components include TCPClient, TCPListener, and UdpClient classes. Underlying data structures: byte arrays and streams.

TCP uses streams for continuous data transmission, while UDP uses byte arrays for packet-based communication.

TCP example: TCPClient client = new TCPClient("example.com", 80); networkStream stream = client.GetStream(); byte[] buffer = new byte[1024]; Stream.Read(buffer, 0, buffer.length); // uses byte array for data transfer

TCP enables stable, secure, and orderly transfer between applications over the network, with no corruptions, loss, or errors, and is reliable.

UI Updates and Design Patterns

In C#, the Invoke Method is used to execute a method on the thread that owns the control. This is necessary because the UI controls in Windows Forms are not thread-safe and must only be accessed or updated from the UI thread. It’s called to ensure that updates to the GUI are performed on the UI thread, preventing cross-thread exceptions.

The Magic Button Design Anti-Pattern is when a single action (button, function, or feature) performs multiple unrelated tasks in a way that is not clear to the user. For example, if there was a button that said “enter”, but the button also sent you an email, sent something to the server, and deleted your history folder. This is a problem for testing, and the user is frustrated.

Git and Data Handling

The .gitignore file is used to prevent unnecessary files from being tracked by the Git repo, avoid clutter in the repo, prevent personal info from being committed, and improve performance.

ORM (Object-Relational Mapping): The term for the process of converting database data into C# objects.

Data Mapper: The underlying mechanism that supports ORM.

Yield: The yield statement is used to implement iterators that return values lazily. It helps manage memory efficiently, improves performance by delaying computation, and simplifies iteration logic. It creates an iterator that implements IEnumerable and IEnumerable<T>.

Yield Example: Static IEnumerable<int> GetEvenNumbers(int max) { for (int i = 0; i <=max; i++) { if (i % 2 == 0) { yield return i; // returns one even number at a time } }

Dependency Injection (DI) is a design pattern that deals with how objects or components in a system obtain references to the services or dependencies they need to function. Instead of a class directly creating instances of its dependencies, the dependencies are “injected” into the class, typically through its constructor, properties, or methods.

DI Example: ILogger logger = new ConsoleLogger();

SQL and Logging

SQL Query: A structured command used to interact with a relational database. It allows users to perform various operations such as retrieving, updating, inserting, or deleting data from one or more database tables.

Patrons

Card Num

Harry

123

Ron

623

Malfoy

234

Card Num

Phone Num

123

123-1123

623

555-5555

234

765-4321

This would be a good example of a SQL Query table because you can see no reason for repeated data, and no confusion.

To join the patrons table with the phone numbers table and retrieve mapping from a patron’s name to phone number, you can use the SQL JOIN clause:

SELECT Patrons.name as PatronName, Phone.phone_num AS phoneNumber FROM Patrons JOIN Phones ON Patrons.card_num = Phones.card_num;

New Example:

emp_id

emp_name

dep_id

This

1

Harry

D1

Is

dept_id

dept_name

2

Ron

D1

A

D1

IT

3

Hermione

D2

New

D2

HR

4

malfoy

D3

Table

D3

Finance

Task: fetch the employee name and department name they belong to:

SELECT e.emp_name, d.department_name FROM employee e inner join department d on e.dept_id = d.dept_id;

Logging: Is the process of recording information about the program’s execution to understand its behavior, identify problems, and track important events. Logs typically include data such as timestamps, event severity, and event details.

Used for: Debugging, monitoring app performance, and audit trails of operations.

Levels: Trace, Debug, Info, Warning, Error, and Critical.

Example from assignments:

public static void HandleConnection(NetworkConnection connection) { try { string request = connection.ReadLine(); Logger.LogInformation("Request Received: {Request}", request); //Right here string response = string.Empty; // ... process request and generate response connection.Send(response); Logger.LogInformation("Response sent successfully."); // Right here } catch (Exception ex) { Logger.LogError(ex, "Error handling connection."); // Right here } }

UML and Multithreading

UML Diagram: Is a visual representation used to model the structure, behavior, and interactions within a software system or processes. It is a standardized way of depicting different aspects of a system, helping developers, architects, and stakeholders communicate on the design and functionality of a system.

Like imagine one class “Customer” that has name, address, place order, and cancel order, and you have an order class with orderID, TimeOfTransaction, and Calculate Total. You will show in a diagram how those two classes work together to create an online pizza website. Write the Class title on top, and the methods of them.

In a program like the Snake Client, multiple threads might be running concurrently to handle different tasks. These might include:

UI Thread (Main Thread): This thread is responsible for rendering the game display, handling user input, and updating the UI. It often runs the main event loop and handles the user interface in games or applications.

Game Logic Thread: This thread could handle the main game loop, including snake movement, collision detection, score tracking, and other game mechanics. It might need to run at a fixed time interval to maintain smooth gameplay.

Networking Thread: If the game supports multiplayer or server communication, this thread is responsible for managing network communications, sending and receiving data packets, and updating the game state based on information received from the server.

Delegates and Memory Management

Delegate Example:

Consider a student class with a method that computes the student’s GPA. What is the data type of x: var x = Students.ComputaGPA();

This would either be a Delegate or a double. If ComputeGPA was an instance method, it would be a delegate, but if it was a property method, then it would be a double.

Memory Management: Is a critical component that involves efficient allocation, use, and deallocation of memory resources. It’s essential for ensuring that a program runs without wasting memory or causing memory errors. Here are some types:

  • Heap Memory: This is used for dynamically allocated memory. It’s where objects or variables are created during the runtime of a program, such as through new or malloc. The memory here is managed at runtime and needs explicit deallocation when it’s no longer needed.
  • Stack Memory: This is used for static memory allocation, such as function call frames and local variables. It’s automatically managed — when a function is called, memory is “pushed” to the stack, and when it returns, memory is “popped” off.
  • Static Memory: This is reserved for variables that have a fixed size and lifetime throughout the program’s execution. These include global variables, static variables, and constants.

Memory Allocation:

Static Allocation: The memory size is determined before execution. Examples include global variables and fixed-size arrays.

Dynamic Allocation: Memory is allocated during the program’s execution. It allows for flexible use of memory based on runtime requirements.

In languages like Java, C#, and Python, garbage collection (GC) is an automatic memory management feature that frees up memory used by objects that are no longer reachable or needed by the program.

Memory leak: Occurs when a program fails to release memory that is no longer needed. This happens when a program allocates memory but doesn’t properly deallocate it, resulting in the memory being unavailable for future use. Over time, memory leaks can accumulate and cause the system to run out of memory, leading to crashes or performance degradation.

Immutability and ACID Properties

Immutable refers to an object or data structure whose state or value cannot be modified after it has been created. Once an immutable object is instantiated, its fields or properties cannot be changed. This concept applies to both data structures (like strings, lists, and arrays) and objects in programming languages.

Unmodifiable State: An immutable object’s state is fixed and cannot be altered after its creation. Any operation that seems to modify the object actually creates a new instance instead of modifying the original one.

Thread Safety: Since immutable objects can’t be changed, they are inherently thread-safe. Multiple threads can read from an immutable object without needing synchronization because no thread can alter the object’s state.

Example:

string original = "hello"; original = original + " world"; // This creates a new string Console.WriteLine(original); // Outputs "hello world"

Immutable Lists in C# or Java are designed so that any operation that would modify the list (like adding or removing an item) instead returns a new list, leaving the original list unchanged.

ACID stands for: Atomicity, Consistency, Isolation, and Durability.

What is Atomicity?
Atomicity ensures that a database transaction is treated as a single, indivisible unit of work. This means that either all of the operations within the transaction are completed successfully, or none of them are. If any part of the transaction fails, the entire transaction is rolled back, and the database is left in a consistent state.

Why is Atomicity Important?
Atomicity is important because it guarantees that partial changes to the database do not occur, which could lead to inconsistent or incorrect data. For example, in a banking application, if a transfer involves subtracting money from one account and adding it to another, atomicity ensures that if the transfer process is interrupted (e.g., due to a system crash), the transaction is either fully completed or fully undone. Without atomicity, you could end up with situations where the money is deducted from one account but not added to the other, leading to data corruption or loss. In short, atomicity helps maintain the integrity and reliability of the database, ensuring that transactions are processed in a way that avoids partial updates.

Definition: Consistency ensures that a transaction will bring the database from one valid state to another, maintaining the integrity of the data. This means that a transaction must follow all the rules (e.g., constraints, triggers, and other database rules) defined for the system.

Consistency guarantees that all data entered into the database adheres to the business rules and constraints (e.g., a user cannot have a negative balance). If consistency wasn’t enforced, it would be possible to have invalid or contradictory data, which could lead to errors in the system and loss of trust in the database.

Serialization Pitfalls

A common pitfall when serializing in C# is not handling null values properly. This can lead to unexpected behavior or errors during serialization and deserialization.

static void Main() { Person person = new Person { Name = "Jane", Age = 25 }; string json = JsonSerializer.Serialize(person); Console.WriteLine("Serialized JSON:"); Console.WriteLine(json); Person deserializedPerson = JsonSerializer.Deserialize<Person>(json); Console.WriteLine("\nDeserialized object:"); Console.WriteLine($"Name: {deserializedPerson.Name}, Age: {deserializedPerson.Age}"); }