Live Coding
17.04.2024 | https://youtu.be/kKcwi0w5c8A |
31.10.2023 | https://youtu.be/d4iq9-ZxqCw |
28.04.2023 | https://youtu.be/KH0vLN1siI8 |
04.07.2022 | https://youtu.be/JaZ_1aXlX5A |
15.03.2021 | https://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()
}