Wednesday, October 30, 2019

Intro to Kotlin, Part 2 Outline

Let's start out with showing how classes work in Kotlin. Here's a basic User class:
class User(val username: String)

fun main() {
  val user = User("bob")
  println(user.username)
}
  • In one line we've declared a class with a property on that class
  • Note that public is the default visibility
  • Also note no new keyword when creating an instance
  • Since val was used to define the username, you can only get the value, and not change it
  • If you want to change it (e.g. have a setter), then use var
class User(var username: String)

fun main() {
  val user = User("bob")
  user.username = "robert"
  println(user.username)
}
  • Also, you can set default values for class properties
class User(var username: String = "unknown")

fun main() {
  val user = User()
  println(user.username)
}
  • You can also define multiple classes in the same file
class User(val username: String)
class Comment(val message: String, val author: User)

fun main() {
  val user = User("bob")
  val comment = Comment("Hi there!", user)
}
  • Classes like these that only contain data are called value objects, and are frequently known as pojos in Java
  • Typically with these kinds of classes, you should implement the toString, equals, and hashCode methods
  • In Kotlin, these methods can be auto implemented for you by declaring the class a data class
class User(val username: String)
class Comment(val message: String, val author: User)

fun main() {
  val user1 = User("bob")
  val comment1 = Comment("Hi there!", user1)

  val user2 = User("bob")
  val comment2 = Comment("Hi there!", user2)

  println(comment1)
  println(comment2)
  println(comment1 == comment2)
  println(comment1.hashCode())
  println(comment2.hashCode())
}
compared to
data class User(val username: String)
data class Comment(val message: String, val author: User)

fun main() {
  val user1 = User("bob")
  val comment1 = Comment("Hi there!", user1)

  val user2 = User("bob")
  val comment2 = Comment("Hi there!", user2)

  println(comment1)
  println(comment2)
  println(comment1 == comment2)
  println(comment1.hashCode())
  println(comment2.hashCode())
}
Let's look at something a little more complicated:
class User(val username: String)
class Comment(initialMessage: String, val author: User) {

  private val msgHist = mutableListOf(initialMessage)

  init {
    println("Logging initial message: $initialMessage")
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • We didn't want initialMessage to be a property, so ommitted the val/var, making it just a parameter
  • Parameters can be used in the init block or to initialize properties
  • Properties can have custom accessors
  • Here's an example of a function in a class
  • Also note that if you need more than the primary constructor, you can create secondary constructors like this:
class User(val username: String)
class Comment(messageHistory: List<String>, val author: User) {

  private val msgHist = messageHistory.toMutableList()

  init {
    println("Logging initial messages: $messageHistory")
  }

  constructor(initialMessage: String, author: User): this(listOf(initialMessage), author) {
    println("Secondary constructor used")
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • You are also not required to have a primary constructor
class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  val author: User

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • Also, you can have different visibilities for the get and set accessors
class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  var author: User
    private set

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }

  fun anonymize() {
    author = User("anonymous")
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • And you can access the backing field for a property with the field keyword
data class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  var author: User
    private set(value: User) {
      println("User changed from $field to $value")
      field = value
    }

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }

  fun anonymize() {
    author = User("anonymous")
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
  comment.anonymize()
}
  • You can also create infix functions
class Thread() {
  val comments = mutableListOf<Comment>()
}
data class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  var author: User
    private set(value: User) {
      println("User changed from $field to $value")
      field = value
    }

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }

  fun anonymize() {
    author = User("anonymous")
  }

  infix fun on(thread: Thread) {
    thread.comments.add(this)
  }
}

fun main() {
  val thread = Thread()
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
  comment.anonymize()
  comment on thread
}
Visibility
  • Let's touch on visibility real quick
Modifier Class member Top-level declaration
public (default) Visible everywhere Visible everywhere
internal Visible in a module Visible in a module
protected Visible in subclass --
private Visible in a class Visible in a file

Enums
  • Enums are declared via enum class, other than that they're the same as in Java
enum class Colors {
  RED,
  GREEN,
  BLUE
}
Interfaces
  • Interfaces work very similarly to interfaces in Java
interface Clickable {
  fun click()
}

class Button : Clickable {
  override fun click() = println("Click!")
}

fun main() {
  Button().click()
}
  • Note the colon used to specify interface implementation
  • Also note the required override keyword
Inheritance
  • Unlike Java, Kotlin classes are final by default. Use the open keyword to make a class inheritable
  • Also, functions must be marked as open to be overridable
open class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

class Dog : Animal() {
  override fun speak() {
    println("Bark")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val animal = Animal()
  animal.speak()
  animal.sleep()

  val dog = Dog()
  dog.speak()
  dog.sleep()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
  • You can also use the final keyword for a final override
open class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

open class Dog : Animal() {
  final override fun speak() {
    println("Bark")
  }
}

class Bulldog : Dog() {
  //Can't override speak()

  fun growl() {
    println("Grr")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val animal = Animal()
  animal.speak()
  animal.sleep()

  val dog = Dog()
  dog.speak()
  dog.sleep()

  val bulldog = Bulldog()
  bulldog.speak()
  bulldog.sleep()
  bulldog.growl()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
  • You can also create abstract classes, where the base class can't be instantiated
abstract class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

class Dog : Animal() {
  override fun speak() {
    println("Bark")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val animal = Animal() //This won't compile

  val dog = Dog()
  dog.speak()
  dog.sleep()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
  • You can also create sealed classes, where subclasses are required to be defined in the same file
  • This limits subclasses to a pre defined set of classes
sealed class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

class Dog : Animal() {
  override fun speak() {
    println("Bark")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val dog = Dog()
  dog.speak()
  dog.sleep()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
Class delegation and the by keyword
  • Most experts encourage composition over inheritance, but many languages don't make composition easy
  • Kotlin addresses this
interface Door {
  fun enter()
}

class WoodDoor : Door {
  override fun enter() {
    println("Entered by door")
  }
}

interface Window {
  fun openWindow()
}

class ClearWindow : Window {
  override fun openWindow() {
    println("Opened the window")
  }
}

class House : Door by WoodDoor(), Window by ClearWindow()

class Shed(door: Door) : Door by door

class WindowDisplay(window: Window = ClearWindow()) : Window by window

fun main() {
  val house = House()
  house.enter()
  house.openWindow()

  val shed = Shed(WoodDoor())
  shed.enter()

  val windowDisplay = WindowDisplay()
  windowDisplay.openWindow()
}
Extension functions and properties
  • You can, in essence, extend classes that you don't have control over through extension functions
fun main() {
  val helloWorld = "Hello World"
  println(helloWorld.removeVowels())
  println(helloWorld.firstVowel)
}

val vowels = listOf('a', 'e', 'i', 'o', 'u')

fun String.removeVowels() =
  this.filter { it.toLowerCase() !in vowels }

val String.firstVowel
  get() = filter { it.toLowerCase() in vowels }.first()
  • You can also use this to give classes certain functionality only in the right circumstances
fun main() {
  val helloWorld = "Hello World"
  println(helloWorld.removeVowels())
  println(helloWorld.firstVowel)  //This won't work
  MySpecialClass(helloWorld) //Inside the class it will work
}

val vowels = listOf('a', 'e', 'i', 'o', 'u')

fun String.removeVowels() =
  this.filter { it.toLowerCase() !in vowels }

class MySpecialClass(str:String) {
    
    init {
      println(str.firstVowel)
    }
    
    private val String.firstVowel
      get() = filter { it.toLowerCase() in vowels }.first()
}
Lambdas
fun main() {
  val incrementer = { a:Int -> a + 1 }
  println(incrementer(1))
}
  • You define a lambda by surrounding it with curly braces
  • You define the parameters on the left side of the arrow
  • Lamdas can be multiline, and the result of the last statement is returned
fun main() {
  val incrementer = { a:Int ->
    println("Input: $a")
    a + 1
  }
  println(incrementer(1))
}
  • The Kotlin standard library has lots of predefined functions that use lambdas, such as map and filter
fun main() {
  val incrementer = { a:Int ->
    println("Input: $a")
    a + 1
  }
  println(listOf(1, 2, 3).map(incrementer))
}
  • You can of course pass the lambda into the method directly
fun main() {
  println(listOf(1, 2, 3).map({ a:Int ->
    println("Input: $a")
    a + 1
  }))
}
  • And since it is being passed in, you can infer the parameter type
fun main() {
  println(listOf(1, 2, 3).map({ a ->
    println("Input: $a")
    a + 1
  }))
}
  • And when there's only one parameter, Kotlin will provide a default parameter name that you can use, called it
fun main() {
  println(listOf(1, 2, 3).map({
    println("Input: $it")
    it + 1
  }))
}
  • When a lambda is the last parameter for a method, the lambda can be moved outside of the parentheses
fun main() {
  println(listOf(1, 2, 3).map() {
    println("Input: $it")
    it + 1
  })
}
  • And when a lambda is the only parameter for a method, you can remove the parentheses entirely
fun main() {
  println(listOf(1, 2, 3).map {
    println("Input: $it")
    it + 1
  })
}
  • Why does Kotlin support this? Because it allows you to create constructs that look like they're part of the language. For example:
fun main() {
  ifnot (false) {
    println("here")
  }
}

fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • And here we also see how to create functions that take lambdas as parameters
  • body is a lambda that has no parameters, so it uses ()
  • It also returns nothing, so it specifies the return type Unit
  • To specify parameters and return types, we can do something like this:
fun main() {
  val result = 1.singleMap { it > 0 }
  println(result)
}

fun <T, R> T.singleMap(mapper:(T) -> R) =
  mapper(this)
  • Note that parenthesis around the parameter types is always required
  • We also get a small taste of generics here
  • We can also return a lambda from a function, like this:
fun main() {
  val add1 = curry(::add, 1)
  println(add1(2))
}

fun add(a:Int, b:Int):Int = a + b

fun <T, U, R> curry(function:(T, U) -> R, t:T): (U) -> R {
  return { u ->
    function(t, u)
  }
}
  • Also note that we can pass regularly defined functions as lambdas by prepending them with ::
  • You can also inline functions, and the compiler will inline the code with the lambda at compile time
fun main() {
  ifnot (false) {
    println("In ifnot")
  }
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • This allows for some speedups, since lambda objects don't need to be created at runtime
  • Note that an inlined function cannot call and pass its lambda to another function unless that function is also inlined
  • It also allows you do to some extra things in your lambda, such as using a return statement
fun main() {
  ifnot (false) {
    println("In ifnot")
    return
    println("After return") //It won't reach this code
  }
  println("After ifnot") //It won't reach this code, either
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • Note that if you wanted to just return from the lambda, you can do so as follows:
fun main() {
  ifnot (false) {
    println("In ifnot")
    return@ifnot
    println("After return") //It won't reach this code
  }
  println("After ifnot") //But it will reach this code
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • You can also specify a label, instead of using the method name:
fun main() {
  ifnot (false) marker@{
    println("In ifnot")
    return@marker
    println("After return") //It won't reach this code
  }
  println("After ifnot") //But it will reach this code
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • You can also create lambdas with receivers, which bind the this keyword to something specific in the lambda
  • You can think of them as extension functions as lambdas
  • Here's how the with statement in the standard library works (note that the standard library with statement has a little more to it)
fun main() {
  val result = with("Hello World") {
    substring(6)
  }
  println(result)
}

inline fun <T, R> with(receiver:T, block:T.() -> R): R {
  return receiver.block()
}

Tuesday, October 29, 2019

Accelerate Chapter 9 Discussion Points

Chapter 9 of Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations, is about making work sustainable.

  • Burnout and deployment pain are important issues to address
  • Deployment pain
    • Highlights the friction and disconnect that exist between
      • The activities used to develop and test software and 
      • The work done to maintain and keep software operational
    • This is where development meets IT operations
    • Where code deployments are most painful, you'll find the poorest software delivery performance, organizational performance, and culture
    • It is also a concern when development and test teams have no visibility into code deployments
      • If they aren't aware of the deployment process, there are probably barrier hiding the work from them
      • Barrier that hide the work of deployment from developers are rarely good, because they isolate developers from the downstream consequences of their work
    • Teams reduce deployment pain when they improve key technical capabilities, such as:
      • Implement comprehensive test and deployment automation
      • Use continuous integration, including trunk-based development
      • Shift left on security
      • Effectively manage test data
      • Use loosely coupled architectures
      • Can work independently
      • Use version control of everything required to reproduce production environments
    • The technical practices that improve our ability to deliver software with both speed and stability also reduce the stress and anxiety associated with pushing code to production
    • There's a high correlation showing that the more painful the code deployments are, the poorer the IT performance, organizational performance, and organizational culture
    • Most deployment problems are caused by a complex, brittle deployment process stemming from these factors:
      • Software is often not written with deployability in mind
      • Manual changes must be made to production environment as part of the deployment process
      • Require multiple handoffs between teams, particularly in siloed organizations
    • To reduce deployment pain:
      • Build systems that:
        • Are designed to be deployed easily into multiple environments
        • Can detect and tolerate failures in their environments
        • Can have various components of the system updated independently
      • Ensure that the state of production systems can be reproduced (with the exception of production data) in an automated fashion from information in version control
      • Build intelligence into the application and the platform so that the deployment process can be as simple as possible
    • Applications designed for a platform-as-a-service can typically be deployed using a single command
  • Burnout
    • Deployment pain can lead to burnout if left unchecked
    • It is physical, mental, or emotional exhaustion caused by overwork or stress
    • It can make things we once loved about our work and life seem insignificant and dull
    • It often manifests as a feeling of helplessness, and is correlated with pathological cultures and unproductive, wasteful work
    • Research shows that stressful jobs can be as bad for physical health as secondhand smoke and obesity
    • Stress costs the US economy $300 billion per year in sick time, long-term disability, and excessive job turnover
    • Organizations can fix the conditions that lead to burnout by 
      • Fostering a supportive work environment
      • Ensuring work is meaningful
      • Ensuring employees understand how their own work ties to strategic objectives
    • Managers should concentrate on:
      • Fostering a respectful, supportive work environment that emphasizes learning from failures rather than blaming
      • Communicating a strong sense of purpose
      • Investing in employee development
      • Asking employees what is preventing them from achieving their objectives and then fixing those things
      • Giving employees time, space, and resources to experiment and learn
  • Common problems that can lead to burnout
    • Six organizational risk factors that predict burnout:
      • Work overload: job demand exceed human limits
      • Lack of control: inability to influence decisions that affect your job
      • Insufficient rewards: insufficient financial, institutional, or social rewards
      • Breakdown of community: unsupportive workplace environment
      • Absence of fairness: lack of fairness in decision-making processes
      • Value conflicts: mismatch in organizational values and the individual's values
    • Most organizations try to fix the person and ignore the work environment, even though fixing the environment has a higher likelyhood of success
  • How to reduce or fight burnout
    • Five organizational factors that are highly correlated with high levels of burnout:
      • Organizational culture
        • Managers are responsible for fostering a supportive and respectful work environment
        • They can do so by
          • Creating a blame-free environment
          • Striving to learn from failures
          • Communicating a shared sense of purpose
          • Remembering that human error is never the root cause of failure in systems
      • Deployment pain
        • With the right practices in place, deployments don't have to be painful events
        • Managers should ask their teams how painful their deployments are and fix the things that hurt the most
      • Effectiveness of leaders
        • Responsibilities of a team leader include limiting work in process and eliminating roadblocks for the team so they can get their work done
      • Organizational investments in DevOps
        • Organizations that invest in developing the skills and capabilities of their teams get better outcomes
        • Investing in training and providing people with the necessary support and resources (including time) to acquire new skills are critical to the successful adoption of DevOps
      • Organizational performance
        • Lean management and continuous delivery practices improve software delivery performance
        • At the heart of Lean management is giving employees the necessary time and resources to improve their own work
        • This means creating a work environment that:
          • Supports experimentation, failure, and learning
          • Allows employees to make decisions that affect their jobs
          • Creating space for employees to do new, creative, value-add work during the work week, and not just expecting them to devote extra time after hours
    • By aligning organizational values with individual values, employee burnout can be reduced

Tuesday, October 22, 2019

Accelerate Chapter 8 Discussion Points

In chapter 8 of Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations, we discuss product development:

  • It is important to take an experimental approach to product development. This includes:
    • Building and validating prototypes from the beginning
    • Working in small batches
    • Evolving or "pivoting" products and the business models behind them early and often
  • Lean product development practices
    • Four capabilities of a Lean approach to product development:
      • Work in small batches
        • Slice up features into small batches that can be completed in less than a week and released frequently, including the use of MVPs
      • Make flow of work visible
        • Teams have a good understanding and visibility of the flow of work from the business all the way through to customers
      • Gather & implement customer feedback
        • Actively and regularly seek customer feedback and incorporate this feedback
      • Team experimentation
        • Teams have the authority to create and change specifications without requiring approval
    • Research over the years shows that software delivery performance predicts Lean product management practices
    • Improving your software delivery effectiveness will improve your ability to work in small batches and incorporate customer feedback along the way
    • Gathering customer feedback includes multiple practices:
      • Regularly collecting customer satisfaction metrics
      • Actively seeking customer insights on the quality of products and features
      • Using this feedback to inform the design of products and features
    • The extent to which teams actually have authority to respond to this feedback is also important
  • Team experimentation
    • One of the points of Agile development is to seek input from customers throughout the delivery process, including early stages
    • If a team isn't allowed to change specifications or requirements without outside authorization, then their ability to innovate is sharply inhibited
    • To be effective, experimentation should be combined with:
      • Working in small batches
      • Making the flow of work through the delivery process visible to everyone
      • Incorporating customer feedback into the design of products
    • This ensures that your teams: 
      • Are making well-reasoned, informed choices
      • Are making changes based on feedback
      • Informed decisions are communicated throughout the organization
  • Effective product management drives performance
    • Lean product management practices positively impact software delivery performance
    • Software delivery performance drives Lean product management practices
    • This is a reciprocal model, or a virtuous cycle
    • These two things together drive better outcomes for your organization
    • The ability to work in small batches is especially important because it enables teams to integrate user research into product development and delivery
    • The ability to take an experimental approach to product development is highly correlated with the technical practices that contribute to continuous delivery

Accelerate Chapter 7 Discussion Points

Moving on to chapter 7 of Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations we'll discuss management practices:
  • Lean management practices
    • Components of Lean management and its application to software delivery:
      • Limit work in progress (WIP)
        • Use these limits to drive process improvement and increase throughput
        • WIP limits alone don't strongly predict delivery performance, but  rather only when combined with visual displays and a feedback loop from production monitoring tools back to delivery teams
        • WIP limits should lead to:
          • Making obstacles to higher flow visible
          • Removal of these obstacles through process improvement
          • Leading to improved throughput
        • WIP limits are no good if they don't lead to improvements that increase flow
      • Visual management
        • Create and maintain visual displays showing key quality and productivity metrics and current status of work
        • Make these visual displays available to engineers and leaders
        • Align metrics with operational goals
        • Visibility should enable high-quality communication
      • Feedback from production
        • Use data from application performance and infrastructure monitoring tools to make business decisions on a daily basis
      • Lightweight change approvals
        • Further discussed later
    • This ensures that teams don't become overburdened, and exposes obstacles to flow
    • The combination of these practices increase delivery performance, have a positive impact on team culture and performance, and decrease burnout
  • Implement a lightweight change management process
    • Requiring external approval lead to lower performance and failed to increase stability
    • Recommendation is to have a lightweight change approval process based on peer review, such as pair programming or intrateam code review, combined with a pipeline to detect and reject bad changes
    • Risk management teams should be monitoring delivery performance and helping teams improve it by implementing practices that are know to increase stability, quality, and speed

Tuesday, October 15, 2019

Intro to Kotlin, Part 1 Outline

So let's start where we always start, with a hello world application:
fun main() {
  println("Hello world!")
}
A couple of things to note here:
  • Just like in Java, the entry point into the code is with the main method
  • But unlike Java, the main method doesn't need to be in a class
  • It's also not necessary to specify the args parameter, though you can if you need it, like this:
fun main(args: Array<String>) {
  println("Hello world!")
}
Some other important points to note:
  • Functions are defined using the fun keyword
  • Parameters are defined by first specifying the name, followed by a colon and the type
Other important points for functions:
  • You specify the return type using a colon and a type at the end of the function
fun main() {
  println(greeting())
}

fun greeting(): String {
  return "Hello world!"
}
  • For functions that are a single line, you can also use expression bodies
fun main() {
  println(greeting())
}

fun greeting(): String =
  "Hello world!"
  • And for functions that do use an expression body, you can let it infer the return type
fun main() {
  println(greeting())
}

fun greeting() =
  "Hello world!"
  • You can also create parameters with default values
fun main() {
  println(greeting())
}

fun greeting(
  greeting: String = "Hello",
  name: String = "world"
) = "$greeting $name!"
  • And you can choose which parameters you want to provide via named arguments
fun main() {
  println(greeting(name = "Bob"))
}

fun greeting(
  greeting: String = "Hello",
  name: String = "world"
) = "$greeting $name!"
  • You can have a vararg parameter using the vararg keyword
fun main() {
  println(greeting("Hi", "Bob", "Sue"))
}

fun greeting(
  greeting: String = "Hello",
  vararg names: String = arrayOf("world")
) = "$greeting ${names.joinToString(" and ")}!"
  • Also if you want to pass an array into the vararg parameter, you need to explicitly use the spread operator
fun main() {
  val names = arrayOf("Bob", "Sue")
  println(greeting("Hi", *names))
}

fun greeting(
  greeting: String = "Hello",
  vararg names: String = arrayOf("world")
) = "$greeting ${names.joinToString(" and ")}!"
So with that, let's move on to variables:
  • The difference between val and var is that val can only be assigned to once, whereas var allows reassignment
fun main() {
  val myVal = "Test 1"
  myVal = "Test 2" //This line breaks
  
  var myVar = "Test 1"
  myVar = "Test 2" //This works fine
}
  • Kotlin is a statically typed language, but it is able to infer the type if it's being immediately assigned a value
  • If the value isn't immediately specified, then a type must be specified at declaration
fun main() {
  val myVal: String
  myVal = "Test 1"
}
  • Also note that the type cannot be changed
fun main() {
  var myVar = "Test 1"
  myVar = 2 //This is not allowed
}
  • All types in Kotlin come in nullable and non null variants
  • Non null types cannot be null
fun main() {
  val myVal: String = null // This is not allowed
}
  • You make a type nullable by adding a ?
fun main() {
  val myVal: String? = null // This works fine
}
  • You can't do anything with a nullable type until you've null checked it
fun main() {
  val greeting: String? = "Hello world!"
  greeting.substring(1, 3) // This doesn't compile
  if (greeting != null) {
    greeting.substring(1, 3) //This works fine
  }
}
  • There are operators that help to make null checks easier, such as the safe call operator
fun main() {
  val greeting: String? = "Hello world!"
  greeting?.substring(1, 3)
}
  • And the elvis operator
fun main() {
  val greeting: String? = "Hello world!"
  (greeting ?: "Some default").substring(1, 3)
}
Strings
  • You can insert a variable into a string template using the $ followed by the variable name
fun main() {
  println(greeting(name = "Bob"))
}

fun greeting(
  greeting: String = "Hello",
  name: String = "world"
) = "$greeting $name!"
  • And you can insert logic using the $ by sticking it inside {}
fun main() {
  println(greeting("Hi", "Bob", "Sue"))
}

fun greeting(
  greeting: String = "Hello",
  vararg names: String = arrayOf("world")
) = "$greeting ${names.joinToString(" and ")}!"
  • We can also create multi-line strings
fun main() {
  val insert = "Some insert"
  val str1 = """
    |I'm
    |  a multi-line
    |    string
    |      with indentation and and and insert: $insert
  """.trimMargin()
  println(str1)
}
If statements
  • The major difference between if statements in Java and Kotlin is that in Kotlin an if statement can return a value
fun main() {
  val result = if (1 > 2) {
    "Not happening"
  } else {
    "Here's the result"
  }
  println(result)
}
When statements
  • When statements are essentially a much more powerful switch statement
fun main() {
  val myVal: Any = "My Test"
  when (myVal) {
    is String -> println(myVal.substring(3))
    is Int -> println(myVal + 5)
    else -> println("Unknown")
  }

  val myOtherVal = "Test 2"
  val result = when (myOtherVal) {
    "Test 1" -> "Result 1"
    "Test 2" -> "Result 2"
    else -> "Other"
  }
  println(result)

  val result2 = when {
    myOtherVal == myVal -> "Equal"
    myOtherVal.startsWith("Test") -> "Test"
    else -> "Other"
  }
  println(result2)
}
While loops
  • While and do-while loops function the same way as they do in Java
For each loops
  • For loops don't exist in Kotlin, you instead do everything with for each loops
fun main() {
  for (i in 1..5) {
    print("$i ")
  }
  println()

  for (i in 1 until 5) {
    print("$i ")
  }
  println()

  for (i in 5 downTo 1) {
    print("$i ")
  }
  println()

  for (i in 1..5 step 2) {
    print("$i ")
  }
  println()
  println()

  val list = listOf("One", "Two", "Three")
  for (i in 0 until list.size) {
    println("$i: ${list[i]}")
  }
  println()

  for (i in list.indices) {
    println("$i: ${list[i]}")
  }
  println()

  for (item in list) {
    println(item)
  }
  println()
}
Object destructuring
  • You can use object destructuring to directly access the properties on a object
fun main() {
  val map = mapOf("key1" to "value1", "key2" to "value2")

  for (pair in map) {
    println("${pair.key} ${pair.value}")
  }
  println()

  for ((key, value) in map) {
    println("$key, $value")
  }
  println()
}
  • You can also use the underscore to omit properties that you don't care about
fun main() {
  val map = mapOf("key1" to "value1", "key2" to "value2")

  for ((key, _) in map) {
    println(key)
  }
  println()
}
Equality
  • The == in Kotlin is equivalent to the .equals call in Java
  • If you need Java's == functionality, in Kotlin you use ===
Try, catch, finally
  • This works similar to how it works in Java, but you can return a value from it
fun main() {
  val myVal = "Hello world"
  val result = try {
    myVal.toInt()
  } catch (e: Exception) {
    0
  }
  println(result)
}

Accelerate Chapter 6 Discussion Points

Chapter 6 of Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations, is about integrating infosec into the delivery lifecycle:
  • Infosec is vitally important, however, infosec teams:
    •  Are often poorly staffed
    • Are usually only involved at the end of the software delivery lifecycle
  • Furthermore, many developers are ignorant of common security risks and how to prevent them
  • Building security into software development improves both delivery performance and security quality
  • Shifting left on security
    • When teams build information security into the software delivery process instead of making it a separate phase, team's ability to practice continuous delivery is positively impacted
    • What does "shifting left" entail?
      • Security reviews are conducted for all major features, and this review process is performed in such a way that it doesn't slow down the development process
      • Infosec experts should:
        • Contribute to the process of designing applications
        • Attend and provide feedback on demonstrations of the software
        • Ensure that security features are tested as part of the automated test suite
        • Make it easy for developers to do the right things in terms of infosec
    • We see a shift from infosec teams doing the security reviews themselves to giving the developers the means to build security in
  • The rugged movement
    • Rugged software should be resilient in the face of security attacks and threats

Thursday, October 10, 2019

Accelerate Chapter 5 Discussion Points

Chapter 5 of Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations, covers architecture and its impact on continuous delivery practices:

  • The architecture of your software and the services it depends on can be a significant barrier to increasing both the tempo and stability of the release process and the systems delivered
  • Can DevOps and continuous delivery be applied to systems other than web based, such as mainframe, firmware, etc.?
  • High performance is possible with all kinds of systems, provided that systems and teams are loosely coupled
  • Types of systems and delivery performance
    • Software being built corresponded with low performers in only two situations:
      • Software being built, or services to interact with, was custom software developed by another company (e.g., an outsourcing partner)
        • Underlines the importance of bringing this capability in house
      • Mainframe systems
    • Outside of these two cases, there was no significant correlation between software being built and delivery performance
  • Focus on deployability and testability
    • There are two architectural characteristics that are important to achieving high performance:
      • We can do most of our testing without requiring an integrated environment
      • We can and do deploy or release our application independently of other applications/services it depends on
    • To achieve these characteristics, design systems are loosely coupled -- that is, can be changed and validated independently of each other.
    • According to the 2017 analysis, the biggest contributor to continuous delivery was when teams can:
      • Make large-scale changes to the design of their system without the permission of somebody outside the team
      • Make large-scale changes to the design of their system without depending on other teams to make changes in their systems or creating significant work for other teams
      • Complete their work without communicating and coordinating with people outside their team
      • Deploy and release their product or service on demand, regardless of other services it depends upon
      • Do most of their testing on demand, without requiring an integrated test environment
      • Perform deployments during normal business hours with negligible downtime
    • Organizations should evolve their team and organizational structure to achieve the desired architecture
    • The goal is for your architecture to support the ability of teams to get their work done without requiring high-bandwidth communication between teams
    • This doesn't mean that teams shouldn't work together, but it's rather: 
      • To ensure that the available communication bandwidth isn't overwhelmed by fine-grained decision-making at the implementation level
      • So we can instead use that bandwidth for discussing higher-level shared goals and how to achieve them
  • A loosely coupled architecture enables scaling
    • If we achieve a loosely coupled, well-encapsulated architecture with a matching organizational structure:
      • We can achieve better delivery performance, increasing tempo and stability while reducing burnout and pain of deployment
      • We can substantially grow the size of our engineering organization and substantially increase productivity as we do so
    • This is based off the measurement of number of deploys per day per developer
    • As the number of developers increases:
      • Low performers deploy with decreasing frequency
      • Medium performers deploy at a constant frequency
      • High performers deploy at a significantly increasing frequency
  • Allow teams to choose their own tools
    • There is a downside to lack of flexibility: it prevents teams from 
      • Choosing technologies that will be most suitable for their particular needs
      • Experimenting with new approaches and paradigms to solve their problems
    • When teams can decide which tools they use, it contributes to software delivery performance and, in turn, to organizational performance
    • There is a place for standardization, particularly around the architecture and configuration of infrastructure
    • Teams that build security into their work do better at continuous delivery
      • A key element of this is ensuring that information security teams make preapproved, easy-to-consume libraries, packages, toolchains, and processes available
    • When tools provided actually make life easier for the engineers who use them, they will adopt them of their own free will
      • This is a much better approach than forcing them to use tools that have been chosen for the convenience of other stakeholders
      • A focus on usability and customer satisfaction is just as important when building tools for internal customers as it is for external customers
      • Allowing your engineers to choose whether or not to use them ensures that we keep ourselves honest in this respect
  • Architects should focus on engineers and outcomes, not tools or technologies
    • What tools or technologies you use is irrelevant if the people who must use them hate using them, or if they don't achieve the outcomes and enable the behaviors that we care about
    • What is important is enabling teams to make changes to their products or services without depending on other teams or systems

Thursday, October 3, 2019

Accelerate Chapter 4 Discussion Points

Moving on to chapter 4 of Accelerate: The Science of Lean Software and DevOps: Building and Scaling High Performing Technology Organizations, we begin to cover technical practices:

  • Many Agile adoptions have treated technical practices as secondary compared to the management and team practice, but research shows that technical practices play a vital role
  • Continuous delivery practices have a measurable impact on software delivery performance, organizational culture, and other outcome measures
  • What is continuous delivery?
    • A set of capabilities that enable us to get changes to production safely, quickly and sustainably
    • Five key principles of continuous delivery:
      • Build quality in
        • We invest in building a culture supported by tools and people where we can detect issues quickly, so that they can be fixed straight away when they are cheap to detect and resolve
      • Work in small batches
        • By splitting work up into much smaller chunks that deliver measurable business outcomes quickly, we get essential feedback so that we can course correct
      • Computers perform repetitive tasks; people solve problems
        • Reduce cost of pushing out changes by taking repetitive work that takes a long time (regression testing, software deployments, etc.) and invest in simplifying and automating this work
        • Thus we free up people for higher-value problem-solving work
      • Relentlessly pursue continuous improvement
        • High performing teams are never satisfied: they always strive to get better
      • Make the state of system-level outcomes transparent
        • System-level outcomes can only be achieved by close collaboration between everyone involved in the software delivery process
    • In order to implement continuous delivery, we must create the following foundations:
      • Comprehensive configuration management
        • It should be possible to provision our environments, build, test, and deploy in a fully automated fashion purely from version control info
      • Continuous integration
        • Following principles of small batches and building quality in, high-performing teams keep branches short-lived (less than one day's work) and integrate them into trunk/master frequently
      • Continuous testing
        • Because testing is so essential, we should be doing it all the time as an integral part of the development process
        • Automated unit and acceptance tests should be run against every commit
        • Developers should be able to run all automated tests locally in order to triage and fix defects
        • Testers should be performing exploratory testing continuously against the latest builds to come out of CI
        • No one should be saying they are "done" with any work until all relevant automated tests have been written and are passing
  • The impact of continuous delivery
    • Strong impact on software delivery performance
    • Helps to decrease deployment pain and team burnout
    • Teams identify more strongly with the organization they work for
    • Improves culture
    • Lower change fail rates
  • Drivers of continuous delivery
    • Version control
    • Deployment automation
    • Continuous integration
    • Trunk-based development
    • Test automation
    • Test data management
    • Shift left on security
    • Loosely coupled architecture
    • Empowered teams
    • Monitoring
    • Proactive notification
  • The impact of continuous delivery on quality
    • Less time spent on rework or unplanned work
      • Unplanned work: different between "paying attention to the low fuel warning light on an automobile versus running out of gas on the highway"
  • Continuous delivery practices: What works and what doesn't
    • Nine key capabilities that drive continuous delivery
      • Version control
        • What was most interesting was that keeping system and application configuration in version control was more highly correlated with software delivery performance than keeping application code in version control
        • Configuration is normally considered a secondary concern to application code in configuration management, but our research shows that this is a misconception
      • Test automation
        • The following practices predict IT performance:
          • Having automated tests that are reliable
          • Developers primarily create and maintain acceptance tests, and they can easily reproduce and fix them on their development workstations
        • None of this means getting rid of testers
        • Testers perform exploratory, usability, and acceptance testing, and help to create and evolve suites of automated tests by working with developers
      • Test data management
        • Successful teams had adequate test data to run their fully automated test suites and could acquire test data for running automated tests on demand
        • Test data was not a limit on the automated tests they could run
      • Trunk-based development
        • Developing off trunk/master rather than long-lived feature branches was correlated with higher delivery performance
        • Teams that did well:
          • Had fewer than three active branches at any time
          • Their branches had very short lifetimes (less than a day)
          • Never had "code freeze" or stabilization periods
        • These results are independent of team size, organization size, or industry
        • We hypothesize that having multiple long-lived branches discourages both refactoring and intrateam communication
        • Note that open source projects whose contributors are not working on a project full time works differently
      • Information security
        • On high performing teams, the infosec personnel provided feedback at every step of the software delivery lifecycle, from design through demos to helping with test automation
      • Adopting continuous delivery
        • Continous delivery improves both delivery performance and quality, and also helps improve culture and reduce burnout and deployment pain
      • (Here it lists six, and earlier it listed eleven; So what are the nine?)