Live Coding

17.04.2024https://youtu.be/kKcwi0w5c8A
31.10.2023https://youtu.be/d4iq9-ZxqCw
28.04.2023https://youtu.be/KH0vLN1siI8
04.07.2022https://youtu.be/JaZ_1aXlX5A
15.03.2021https://youtu.be/umiL523LE94

Как себя вести?

• Оставайся спокойным. Не обязательно найти идеальное решение. Проверяется сама способность рассуждать.

• Планируй решение. Сразу бросаться писать код - плохая идея. Сначала предложи варианты решения.

• Декомпозируй. Разбей задачу на шаги. Начни с простого решения. Улучшай по мере необходимости.

• Проси уточнения. Если задача не ясна - задавай вопросы.

Что произойдет?

public class Incrementer {
		int count = 0;

		public synchronized void inc() {
				if (count < 10) {
						count++;
						inc();
				}
		}
}

new Incrementer().inc();

Что произойдет?

Ответ: a зайдет в вечный цикл и второй launch ее не остановит.

fun main() = runBlocking {
    val a = launch {
        while (true) {
            Thread.sleep(1000)
            println("Show Loading")
        }
    }

    launch {
        withContext(Dispatchers.IO) {
            delay(5500)
            a.cancel()
            println("Finished")
        }
    }
}

На каком потоке вызывается код в onSuccess?

fun updateRegions() {
		getRegions()
				.subscribeOn(Schedulers.io))
				.observeOn(Schedulers.computation())
				.subscribe(
						{ regions ->
								showRegions(regions)
						}
				)
}

На каком потоке вызывается код в onSuccess?

fun updateRegions() {
		getRegions()
				.subscribe(
						{ regions ->
								showRegions (regions)
						}
}

На каком потоке вызывается код в onSuccess?

fun updateRegions() {
		getRegions ()
				.subscribeOn(Schedulers.io))
				.subscribe(
						{ regions ->
								showRegions(regions)
						}
}

На каком потоке вызывается код в onSuccess?

fun updateRegions() {
		getRegions()
				.subscribeOn(Schedulers.computation())
				.subscribeOn(Schedulers.io())
				.subscribe(
					 { regions ->
								showRegions (regions)
						}
}

На каком потоке вызывается код в onSuccess?

fun updateRegions() {
		getRegions()
				.observen(Schedulers.computation())
				.observeOn(Schedulers.io))
				.subscribe(
						{ regions ->
								showRegions(regions)
						}
				)
}

На каком потоке вызывается код в flatMap?

fun updateRegions() {
		getRegions()
				.subscribeOn(Schedulers.single())
				.observeOn(Schedulers.computation())
				.flatMap {
						getCity()
				}
				.subscribeOn(Schedulers.io))
				.subscribe(
						{ regions ->
								showRegions (regions)
						}
				)
}

На каком потоке вызывается код в doOnSubscribe?

fun updateRegions() {
		getRegions()
				.subscribeOn(Schedulers.single())
				.observen(Schedulers.computation())
				.doOnSubscribe {
						showProgress()
				}
				.subscribeOn(Schedulers.io))
				.subscribe { regions ->
						showRegions(regions)
				}
}

Что здесь происходит? Что выводит print?

fun main() {
		val orderIds: List<Long> = listOf(1, 2, 3)
		loadAllOrders(orderIds)
				.subscribel { orders ->
						val result = orderIds = orders.map(Order::id)
						print(result)
				})
}

private fun loadAllOrders(orderIds: List<Long>): Single<List<Order>>{
		return Observable.fromIterable(orderIds)
				.subscribeOn(Schedulers.io))
				.flatMapSingle(::loadOrderById)
				.toList()
}

Какую бизнес-задачу решает этот код? Как его отрефакторить?

• Переименовать UseCases.

• Передавать в метод listOf<Item> а не result.

• Избавиться от return if чтобы уменьшить вложенность кода.

• Mapper вынести в отдельный класс.

class MainUseCase @Inject constructor(
		private val firstUseCase: FirstUseCase,
		private val secondUseCase: SecondUseCase
) {
		@CheckResult
		fun execute(result: Result): Completable {
				val orderedItems = result.items
				return if (orderedItems.isEmpty()) {
						Completable.complete()
				} else {
						firstUseCase.getCartItems()
								.map { cartItems ->
										orderedItems.mapNotNull { orderItem ->
												cartItems.firstOrNull i it.id == orderItem.id }
										}
												.map { cartItem ->
														CartItemId(
																cartItemId = cartItem.id,
																skuld = cartItem.skuld,
																bundleId = cartItem.bundleId,
																isPrimaryBundleItem = cartItem.isPrimaryBundleItem
														)
												}
										}
								.flatMapCompletable {
										if (it.isEmpty)) {
												Completable.complete()
										}else{
												secondUseCase.deleteItems(it)
										}
								}
				}
		}
}

Написать эффективную модель для работы с разными событиями

Есть стрим эвентов. Некоторые экраны на него подписаны и слушают нужные им события.

Решение:

• Реализация не должна следить сразу за всеми событиями

• Скрывать реализацию фильтрации

• Буферизация?

interface ServiceConnection {
		val event(): Flow<String> // Emit events in JSON format
}

interface EventFilter<E: Input> {
		fun(json: String) -> E?
}

class OptimizedServiceConnection(
		private val serviceConnection: ServiceConnection
) {
		private val eventFilterRegister = mutableMapOf<String, EventFilter<*>>()
		
		fun registerFilter(type: String, eventFilter: EventFilter<E: Input>) {
				...
		}

		fun <E: Input> ServiceConnection.subscribe(type: String): Flow<E> {
				return serviceConnection.events().filter().mapNotNull { eventFilterRegister.getValue(type).filter(it) }
		}
}

sealed class Input(
		val type: String
) {
		data class LogoutInput(
				@SerialName("type") override val type: String
		)
}

Что увидим в консоли?

class State(
    var x = 0
    var y = 0
)

fun main() {
    val state = State()
    
    val t1 = Thread({
        state.x = 1
        println(state.y)
    })
    val t2 = Thread({
        state.y = 1
        println(state.x)
    })
    t1.start()
    t2.start()
    t1.join()
    t2.join()
}

Tinkoff. Live-coding

Рефакторинг кода:

Понять что делает этот код.

object Const {
		val index = 1
}

private var TAG = "ChatSessionController"

open class ChatSessionController(
		private val accountRepository: ChatAccountRepository,
		private val preferencesManager: ChatPreferencesManager
) {
		lateinit var context: Context
		
		companion object {
				@Volatile
				private var user: User? = null
		}
		
		@Synchronized
		fun initChat(): Single<Boolean> {
				val success = false
				val idPrefix = context.getString(R.string.id_prefix)
				var range = accountRepository.getAccounts().size
				for (i in INDEX..range) {
						val account = accountRepository.getAccounts()[i]
						if (account.isOwner) {
								user = User(
										id = idPrefix + account.id,
										name = null,
										phone = null
								)
						}
				}
				if (success == true) {
						preferencesManager.initPreferences()
				}
				Log.d(TAG, "Init Chat, User = $user")
		}
		
		fun logout() {
				Log.d(TAG, "Logout Chat")
				preferencesManager.getPreferences().edit().clear().apply()
		}
}

class User(
		var id: String,
		var name: String? = null,
		var phone: String? = null 
)

interface ChatAccountRepository {
		fun getAccounts(): LinkedList<Account>
}

interface ChatPreferencesManager {
		fun initPreferences()
		fun getPreferences(): SharedPreferences
}

Рефакторинг кода:

private val TAG = "UsersHolderSingleton"
object CONST {
    val INDEX = 1
}
open class UsersHolderSingleton private constructor(
    val usersRepo: Repository,
    val context: Context
) : LoggerProvider() {

    companion object {

        @Volatile
        private var instance: UsersHolderSingleton? = null
    }

    @Synchronized
    fun getInstance(context: Context, val usersRepo: Repository): UsersHolderSingleton {
        if (instance == null) {
            instance = UsersHolderSingleton(usersRepo)
        }
        return instance!!
    }
    var users: LinkedList<UserData> get() = usersRepo.get()
    val executor: ExecutorService = Executors.newFixedThreadPool(5)
    
		fun update(id: String, newPhoneNumber: String) {
        val formattedNumber = context.getString(R.id.formatted_number_pattern, newPhoneNumber)
        val copy: (oldUserData: UserData, phoneNumber: String) -> UserData = { oldUserData, phoneNumber ->
            UserData(
                id = oldUserData.id,
                name = oldUserData.name,
                phoneNumber = phoneNumber
            )
        }
        for (i in INDEX..users.size) {
            if (users[i].id == id) {
                users[i] = copy(&&, formattedNumber)
                logger.tag(TAG).d(users[i])
            }
        }
    }
}

class UserData(
		var id: String, 
		var name: String, 
		var phoneNumber: String
)

interface Repository {
    fun get(): LinkedList<UserData>
}

Рефакторинг кода:

class ScreenViewModel(
    var confligs: ConfigRepository,
    var holder: UserHolder,
    var cards: CardRepository,
    var analytics: AnalyticsService,
    var context: Context,
): ViewModel() {
 
    lateinit var config: Config
    lateinit var card: CardType
 
    var liveData = MutableLiveData<UiModel>()
    var successfulChecks = 0L
 
    init {
        GlobalScope.launch {
            flowOf(configs.loadConfig()).zip(cards.observeAvailableCard(), { it1, it2 -> Pair(it1, it2) })
                .flowOn(Dispatchers.Default)
                .filter {
                    it.second == CardType.PREMIUM && holder.getUserScore() > 42
                        || it.second != CardType.PREMIUM
                        || it.first.specialUsers.contains(holder.getUserId())
                }
                .onEach {
                    config = it.first
                    card = it.second
                }
                .flatMapConcat {
                    flowOf(
                        config.checkBlackList(it.second, holder.getUserId())
                            .apply {
                                successfulChecks++
                            }
                    )
                }.collect {
                    liveData.value = UiModel(
                        if (card == CardType.CREDIG) config.creditTitle
                        else if (card == CardType.DEBIT) config.debitTitle
                        else if (card == CardType.KID) config.kidTitle
                        else if (card == CardType.PREMIUM) context.getString(R.string.congratulations)
                        else "",
                        context.getString(
                            R.string.description_patterns,
                            config.kinderSurprizeCashBack
                        )
                    )
                }
        }
    }
 
    var disposables: CompositeDisposable? = CompositeDisposable()
    override fun onCleared() {
        GlobalScope.launch {
            analytics.sendSuccessfulChecks(successfulChecks)
            super.onCleared()
        }
    }
}
 
interface Config {
    val debitTitle: String
    val creditTitle: String
    val kidTitle: String
    val kinderSurprizeCashBack: Long
    val specialUsers: List<Long>
}
 
interface ConfigRepository {
    suspend fun loadConfig(): Config
 
    /**
     * Checks all user's data for suspicious activity, can take up to 30 seconds
     * @throws ForbiddenCardTypeException if user is not eligible for that card type
     * */
 
    fun checkBlackList(cardType: CardType, userId: Long) BlackList
}
 
interface BlackList {
    var successfulChecks: Long
}
 
interface CardRepository {
 
    /**
     * Fetches most relevant available card, can change in any minute to a premium version!
     * */
    fun observeAvailableCard(): Flow<CardType>
 
}
 
interface UserHolder {
    fun getUserId(): Long
    fun getUserScore(): Long
}
 
interface AnalyticsService {
    @FormUrlEncoded
    @POST("v1/analytics")
    suspend fun sendSuccessfulChecks(checks: Long)
}
 
enum class CardType {DEBIT, CREDIT, KID, PREMIUM}
 
class UiModel(val title: String, val description: String)
Ozon. Live-coding

Q: Что произойдет?

A: корутина a зайдет в бесконечный цикл и будет печатать «‎Show loading»‎‎ каждую секунду. Вторая корутина не будет запущена. Если заменить Thread.sleep на delay то вторая корутина отменит первую через 5.5 сек.

fun main() = runBlocking {
    val a = launch {
        while (true) {
            Thread.sleep(1000)
            println("Show Loading")
        }
    }

    launch {
        withContext(Dispatchers.IO) {
            delay(5500)
            a.cancel()
            println("Finished")
        }
    }
}

Avito. Live-coding

Q: Что выведет код?

А: Нет гарантий видимости переменных и атомарности операций, поэтому результат может любой(0 0, 1 0, 0 1, 1 1).

Q: Почему результат можем быть 0, 0?

А: У потоков могут могут совпасть линии кэшей или может произойти реордеринг.

Q: Как избежать результата 0, 0?

А: Нужно использовать пометку @Volatile на переменных x,y и сделать и @Synchronized сеттеры.

Q: Как сделать так, чтобы код всегда выводил 1 1?

А: Решение: CountDownLatch

class State(
    var x: Int = 0,
    var y: Int = 0
)

fun main() {
    val state = State()

    val t1 = Thread {
        state.x = 1
        println(state.y)
    }

    val t2 = Thread {
        state.y = 1
        println(state.x)
    }
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
}

// Решение: CountDownLatch
class State {
    @Volatile
    var x: Int = 0
        @Synchronized set

    @Volatile
    var y: Int = 0
        @Synchronized set
}

fun main() {
    val state = State()

    val latch1 = CountDownLatch(1)
    val latch2 = CountDownLatch(1)

    val t1 = Thread {
        state.x = 1
        latch1.countDown()
        latch2.await()
        println(state.y)
    }

    val t2 = Thread {
        state.y = 1
        latch2.countDown()
        latch1.await()
        println(state.x)
    }
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
}